Eu gostaria de otimizar minhas consultas para analisar mysql-slow.log
.
A maioria das minhas consultas lentas contém ORDER BY RAND()
. Não consigo encontrar uma solução real para resolver este problema. Existe uma solução possível em MySQLPerformanceBlog, mas não acho que seja suficiente. Em tabelas mal otimizadas (ou atualizadas com frequência, gerenciadas pelo usuário), ele não funciona ou preciso executar duas ou mais consultas antes de selecionar minha PHP
linha aleatória gerada.
Existe alguma solução para este problema?
Um exemplo fictício:
SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
ORDER BY
RAND()
LIMIT 1
mysql
random
performance
Fabrik
fonte
fonte
Respostas:
Experimente isto:
Isso é especialmente eficiente ativado
MyISAM
(uma vez queCOUNT(*)
é instantâneo), mas mesmoInnoDB
assim é10
mais eficiente do queORDER BY RAND()
.A ideia principal aqui é que não ordenamos, mas, em vez disso, mantemos duas variáveis e calculamos o
running probability
de uma linha a ser selecionada na etapa atual.Veja este artigo no meu blog para mais detalhes:
Atualizar:
Se você precisar selecionar apenas um único registro aleatório, tente o seguinte:
Isso pressupõe que os seus
ac_id
estão distribuídos mais ou menos uniformemente.fonte
@fabrik
: tente agora. Seria muito útil se você postasse os scripts de tabela para que eu pudesse verificá-los antes de postar.Depende de quão aleatório você precisa ser. A solução que você vinculou funciona muito bem IMO. A menos que você tenha grandes lacunas no campo ID, ainda é bastante aleatório.
No entanto, você deve ser capaz de fazer isso em uma consulta usando isto (para selecionar um único valor):
Outras soluções:
random
à mesa e preencha-o com números aleatórios. Você pode então gerar um número aleatório em PHP e fazer"SELECT ... WHERE rnd > $random"
fonte
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
mas isso não parece funcionar corretamente, pois nunca retorna o último registroSELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
Parece estar funcionando para mimÉ assim que eu faria:
fonte
OFFSET
(que é@r
para isso) não evita uma varredura - até uma varredura completa da tabela.(Sim, eu vou ser condenado por não ter carne suficiente aqui, mas você não pode ser vegano por um dia?)
Caso: Consecutivo AUTO_INCREMENT sem lacunas, 1 linha retornada
Caso: Consecutivo AUTO_INCREMENT sem lacunas, 10 linhas
Caso: AUTO_INCREMENT com lacunas, 1 linha retornada
Caso: coluna Extra FLOAT para randomização
Caso: coluna UUID ou MD5
Esses 5 casos podem ser muito eficientes para grandes tabelas. Veja meu blog para os detalhes.
fonte
Isso lhe dará uma única subconsulta que usará o índice para obter um id aleatório e, em seguida, a outra consulta será acionada obtendo sua tabela associada.
fonte
A solução para o seu exemplo fictício seria:
Para ler mais sobre alternativas para
ORDER BY RAND()
, você deve ler este artigo .fonte
Estou otimizando muitas consultas existentes em meu projeto. A solução da Quassnoi me ajudou a agilizar muito as consultas! No entanto, acho difícil incorporar a referida solução em todas as consultas, especialmente para consultas complicadas envolvendo muitas subconsultas em várias tabelas grandes.
Portanto, estou usando uma solução menos otimizada. Basicamente, ele funciona da mesma maneira que a solução de Quassnoi.
$size * $factor / [accomodation_table_row_count]
calcula a probabilidade de escolher uma linha aleatória. O rand () irá gerar um número aleatório. A linha será selecionada se rand () for menor ou igual à probabilidade. Isso efetivamente executa uma seleção aleatória para limitar o tamanho da tabela. Como há uma chance de que ele retorne menos do que o limite de contagem definido, precisamos aumentar a probabilidade para garantir que estamos selecionando linhas suficientes. Portanto, multiplicamos $ size por um $ fator (geralmente defino $ fator = 2, funciona na maioria dos casos). Finalmente fazemos olimit $size
O problema agora é calcular o accomodation_table_row_count . Se soubermos o tamanho da tabela, PODEMOS codificar o tamanho da tabela. Isso seria executado mais rápido, mas obviamente não é o ideal. Se você estiver usando o Myisam, obter a contagem da mesa é muito eficiente. Como estou usando o innodb, estou apenas fazendo uma simples contagem + seleção. No seu caso, seria assim:
A parte complicada é calcular a probabilidade certa. Como você pode ver, o código a seguir, na verdade, apenas calcula o tamanho aproximado da tabela temporária (na verdade, muito aproximado!):
(select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
Mas você pode refinar essa lógica para fornecer uma aproximação mais próxima do tamanho da tabela. Observe que é melhor selecionar OVER do que sub-selecionar as linhas. ou seja, se a probabilidade for definida como muito baixa, você corre o risco de não selecionar linhas suficientes.Esta solução é executada mais lentamente do que a solução de Quassnoi, pois precisamos recalcular o tamanho da tabela. No entanto, acho essa codificação muito mais gerenciável. Esta é uma troca entre precisão + desempenho e complexidade de codificação . Dito isso, em tabelas grandes isso ainda é muito mais rápido do que Order by Rand ().
Nota: Se a lógica da consulta permitir, execute a seleção aleatória o mais cedo possível antes de qualquer operação de junção.
fonte
fonte