Acabei de receber uma consulta bastante complexa com a qual estava trabalhando e estava levando 8 segundos para ser executada. EXPLAIN estava mostrando uma ordem de tabela estranha e meus índices não estavam todos sendo usados, mesmo com a dica FORCE INDEX. Eu encontrei a palavra-chave de junção STRAIGHT_JOIN e comecei a substituir algumas de minhas palavras-chave INNER JOIN por ela. Notei uma melhora considerável na velocidade. Eventualmente, acabei de substituir todas as minhas palavras-chave INNER JOIN por STRAIGHT_JOIN para esta consulta e agora é executado em 0,01 segundos.
Minha pergunta é quando você usa STRAIGHT_JOIN e quando você usa INNER JOIN? Há algum motivo para não usar STRAIGHT_JOIN se você estiver escrevendo boas consultas?
straight_join
.Da referência do MySQL JOIN :
"STRAIGHT_JOIN é semelhante a JOIN, exceto que a tabela à esquerda é sempre lida antes da tabela certa. Isso pode ser usado para aqueles (poucos) casos em que o otimizador de junção coloca as tabelas na ordem errada."
fonte
Aqui está um cenário que surgiu recentemente no trabalho.
Considere três tabelas, A, B, C.
A tem 3.000 linhas; B tem 300.000.000 de linhas; e C tem 2.000 linhas.
As chaves estrangeiras são definidas: B (a_id), B (c_id).
Suponha que você tenha uma consulta semelhante a esta:
select a.id, c.id from a join b on b.a_id = a.id join c on c.id = b.c_id
Na minha experiência, o MySQL pode escolher ir C -> B -> A neste caso. C é menor que A e B é enorme, e são todos equijoins.
O problema é que o MySQL não leva necessariamente em consideração o tamanho da interseção entre (C.id e B.c_id) vs (A.id e B.a_id). Se a junção entre B e C retornar tantas linhas quanto B, então é uma escolha muito ruim; se começar com A tivesse filtrado B para tantas linhas quanto A, então teria sido uma escolha muito melhor.
straight_join
pode ser usado para forçar este pedido desta forma:select a.id, c.id from a straight_join b on b.a_id = a.id join c on c.id = b.c_id
Agora
a
deve ser unido antesb
.Geralmente você deseja fazer suas junções em uma ordem que minimize o número de linhas no conjunto resultante. Portanto, começar com uma mesa pequena e unir de forma que a união resultante também seja pequena é o ideal. As coisas ficam em forma de pêra se começar com uma mesa pequena e juntá-la a uma mesa maior acaba tão grande quanto a mesa grande.
Porém, é dependente de estatísticas. Se a distribuição de dados mudar, o cálculo pode mudar. Também depende dos detalhes de implementação do mecanismo de junção.
Os piores casos que eu vi para o MySQL em que tudo, exceto a
straight_join
sugestão de índice exigida ou agressiva, são consultas que paginam sobre uma grande quantidade de dados em uma ordem de classificação estrita com filtragem leve. O MySQL prefere usar índices para quaisquer filtros e junções em vez de classificações; isso faz sentido porque a maioria das pessoas não está tentando classificar o banco de dados inteiro, mas tem um subconjunto limitado de linhas que respondem à consulta, e classificar um subconjunto limitado é muito mais rápido do que filtrar a tabela inteira, não importa se ela está classificada ou não. Nesse caso, colocar uma junção direta imediatamente após a tabela que tinha a coluna indexada que eu queria classificar em coisas fixas.fonte
straight_join
avalia a mesa da esquerda antes da direita. Portanto, se você quiser ir doA -> B -> C
meu exemplo, a primeirajoin
palavra-chave pode ser substituída porstraight_join
.O MySQL não é necessariamente bom em escolher a ordem de junção em consultas complexas. Ao especificar uma consulta complexa como um straight_join, a consulta executa as junções na ordem em que são especificadas. Colocando a tabela como o mínimo denominador comum primeiro e especificando straight_join, você pode melhorar o desempenho da consulta.
fonte
STRAIGHT_JOIN
, usando esta cláusula, você pode controlar aJOIN
ordem: qual tabela é verificada no loop externo e qual está no loop interno.fonte
Vou te dizer por que tive que usar STRAIGHT_JOIN:
Portanto, forcei uma das junções a ser straight_join para FORÇAR a junção anterior a ser lida primeiro. Isso impediu o MySQL de alterar a ordem de execução e funcionou perfeitamente!
fonte
Em minha curta experiência, uma das situações que
STRAIGHT_JOIN
reduziu minha consulta de 30 segundos para 100 milissegundos é que a primeira tabela no plano de execução não era a tabela que tem a ordem por colunas-- table sales (45000000) rows -- table stores (3) rows SELECT whatever FROM sales INNER JOIN stores ON sales.storeId = stores.id ORDER BY sales.date, sales.id LIMIT 50; -- there is an index on (date, id)
SE o otimizador escolher acertar
stores
primeiro, isso causaráUsing index; Using temporary; Using filesort
porquefonte
aqui, o otimizador precisa de uma ajudinha, dizendo-lhe para acertar
sales
primeiro usandofonte
Se os seus fins de consulta com
ORDER BY... LIMIT...
, ele pode ser o ideal para reformular a consulta para enganar o otimizador a fazer oLIMIT
antes doJOIN
.(Esta resposta não se aplica apenas à pergunta original sobre
STRAIGHT_JOIN
, nem se aplica a todos os casos deSTRAIGHT_JOIN
.)Começando com o exemplo de @Accountant م , isso deve ser executado mais rápido na maioria das situações. (E evita a necessidade de dicas.)
SELECT whatever FROM ( SELECT id FROM sales ORDER BY date, id LIMIT 50 ) AS x JOIN sales ON sales.id = x.id JOIN stores ON sales.storeId = stores.id ORDER BY sales.date, sales.id;
Notas:
INDEX(date, id)
.sales
permite obter apenas 50 "qualquer coisa" sem arrastá-los em uma mesa temporária.ORDER BY
deve ser repetido na consulta externa. (O Otimizador pode encontrar uma maneira de evitar realmente fazer outra classificação.)Eu me oponho ao uso de hits porque "Mesmo que seja mais rápido hoje, pode não ser mais rápido amanhã."
fonte
Eu sei que é um pouco antigo, mas aqui está um cenário, tenho feito script em lote para preencher uma determinada tabela. Em algum ponto, a consulta ficou muito lenta. Parece que a ordem de associação estava incorreta em registros específicos:
A ordem incorreta é executada por cerca de 65 segundos, enquanto o straight_join é executado em milissegundos
fonte
--use 120s, 18 million data explain SELECT DISTINCT d.taid FROM tvassist_recommend_list_everyday_diverse d, tvassist_taid_all t WHERE d.taid = t.taid AND t.client_version >= '21004007' AND t.utdid IS NOT NULL AND d.recommend_day = '20170403' LIMIT 0, 10000 --use 3.6s repalce by straight join explain SELECT DISTINCT d.taid FROM tvassist_recommend_list_everyday_diverse d STRAIGHT_JOIN tvassist_taid_all t on d.taid = t.taid WHERE t.client_version >= '21004007' AND d.recommend_day = '20170403' AND t.utdid IS NOT NULL LIMIT 0, 10000
fonte