Quando usar STRAIGHT_JOIN com MySQL


88

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?

Respostas:


73

Eu não recomendaria usar STRAIGHT_JOIN sem um bom motivo. Minha própria experiência é que o otimizador de consulta do MySQL escolhe um plano de consulta ruim com mais freqüência do que eu gostaria, mas não o suficiente para que você simplesmente o ignore em geral, que é o que você faria se sempre usasse STRAIGHT_JOIN.

Minha recomendação é deixar todas as consultas como JOINs regulares. Se você descobrir que uma consulta está usando um plano de consulta abaixo do ideal, sugiro primeiro tentar reescrever ou reestruturar um pouco a consulta para ver se o otimizador escolherá um plano de consulta melhor. Além disso, pelo menos para o innodb, certifique-se de que não apenas as estatísticas do índice estejam desatualizadas ( ANALYZE TABLE ). Isso pode fazer com que o otimizador escolha um plano de consulta ruim. As dicas do otimizador geralmente devem ser seu último recurso.

Outro motivo para não usar dicas de consulta é que sua distribuição de dados pode mudar com o tempo, ou sua seletividade de índice pode mudar, etc. conforme sua tabela cresce. Suas dicas de consulta que são ideais agora, podem ficar abaixo do ideal com o tempo. Mas o otimizador não será capaz de adaptar o plano de consulta por causa de suas dicas agora desatualizadas. Você ficará mais flexível se permitir que o otimizador tome as decisões.


60
Esta resposta não explica realmente quando usar straight_join .
Pacerier

23

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."


28
Obrigado, mas já li o manual do MySQL sobre ele. Esperando por alguma explicação adicional.
Greg

20

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_joinpode 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 adeve ser unido antes b.

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_joinsugestã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.


Como você usaria a junção direta para resolver o problema?
Hannele

@Hannele straight_joinavalia a mesa da esquerda antes da direita. Portanto, se você quiser ir do A -> B -> Cmeu exemplo, a primeira joinpalavra-chave pode ser substituída por straight_join.
Barry Kelly

Ah legal. Seria útil incluir isso como um exemplo em sua resposta :)
Hannele

18

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.


11

STRAIGHT_JOIN, usando esta cláusula, você pode controlar a JOINordem: qual tabela é verificada no loop externo e qual está no loop interno.


O que são loop externo e loop interno?
Istiaque Ahmed

As tabelas @IstiaqueAhmed são unidas por loops aninhados (pegue a primeira linha da tabela A e faça o loop, lance a tabela B, em seguida, pegue a segunda linha ... e assim por diante. Aqui a tabela A está no loop externo)
Contador desde

6

Vou te dizer por que tive que usar STRAIGHT_JOIN:

  • Tive um problema de desempenho com uma consulta.
  • Simplificando a consulta, a consulta ficou repentinamente mais eficiente
  • Tentando descobrir qual parte específica estava trazendo o problema, simplesmente não consegui. (2 junções à esquerda eram lentas e cada uma era rápida independentemente)
  • Em seguida, executei o EXPLAIN com consulta lenta e rápida (adicionar uma das junções à esquerda)
  • Surpreendentemente, o MySQL mudou totalmente as ordens de JOIN entre as 2 consultas.

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!


2

Em minha curta experiência, uma das situações que STRAIGHT_JOINreduziu 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 filesortporque

se ORDER BY ou GROUP BY contiver colunas de tabelas diferentes da primeira tabela na fila de junção, uma tabela temporária será criada.

fonte

aqui, o otimizador precisa de uma ajudinha, dizendo-lhe para acertar salesprimeiro usando

sales STRAIGHT_JOIN stores

1
(Aumentei sua resposta.)
Rick James

2

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 o LIMIT 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:

  • Primeiro, 50 ids são buscados. Isso será especialmente rápido comINDEX(date, id) .
  • Em seguida, a junção de volta para salespermite obter apenas 50 "qualquer coisa" sem arrastá-los em uma mesa temporária.
  • uma vez que uma subconsulta é, por definição, não ordenada, o ORDER BY deve ser repetido na consulta externa. (O Otimizador pode encontrar uma maneira de evitar realmente fazer outra classificação.)
  • Sim, é mais confuso. Mas geralmente é mais rápido.

Eu me oponho ao uso de hits porque "Mesmo que seja mais rápido hoje, pode não ser mais rápido amanhã."


0

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:

  • Na ordem correta

insira a descrição da imagem aqui

  • Incrementar o id em 1 bagunça o pedido. Observe o campo 'Extra'

insira a descrição da imagem aqui

  • Usar straight_join corrige o problema

insira a descrição da imagem aqui

A ordem incorreta é executada por cerca de 65 segundos, enquanto o straight_join é executado em milissegundos


-5
--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

3
Isso não fornece informações suficientes para descobrir quando as junções diretas são apropriadas.
Hannele
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.