INNER JOIN vs desempenho LEFT JOIN no SQL Server

259

Eu criei o comando SQL que usa INNER JOIN em 9 tabelas, de qualquer forma, esse comando leva muito tempo (mais de cinco minutos). Então, meu pessoal sugeriu que eu mudasse INNER JOIN para LEFT JOIN porque o desempenho de LEFT JOIN é melhor, apesar do que eu sei. Depois que eu mudei, a velocidade da consulta melhorou significativamente.

Gostaria de saber por que o LEFT JOIN é mais rápido que o INNER JOIN?

Meu comando SQL se parece abaixo: SELECT * FROM A INNER JOIN B ON ... INNER JOIN C ON ... INNER JOIN De assim por diante

Atualização: Este é um resumo do meu esquema.

FROM sidisaleshdrmly a -- NOT HAVE PK AND FK
    INNER JOIN sidisalesdetmly b -- THIS TABLE ALSO HAVE NO PK AND FK
        ON a.CompanyCd = b.CompanyCd 
           AND a.SPRNo = b.SPRNo 
           AND a.SuffixNo = b.SuffixNo 
           AND a.dnno = b.dnno
    INNER JOIN exFSlipDet h -- PK = CompanyCd, FSlipNo, FSlipSuffix, FSlipLine
        ON a.CompanyCd = h.CompanyCd
           AND a.sprno = h.AcctSPRNo
    INNER JOIN exFSlipHdr c -- PK = CompanyCd, FSlipNo, FSlipSuffix
        ON c.CompanyCd = h.CompanyCd
           AND c.FSlipNo = h.FSlipNo 
           AND c.FSlipSuffix = h.FSlipSuffix 
    INNER JOIN coMappingExpParty d -- NO PK AND FK
        ON c.CompanyCd = d.CompanyCd
           AND c.CountryCd = d.CountryCd 
    INNER JOIN coProduct e -- PK = CompanyCd, ProductSalesCd
        ON b.CompanyCd = e.CompanyCd
           AND b.ProductSalesCd = e.ProductSalesCd 
    LEFT JOIN coUOM i -- PK = UOMId
        ON h.UOMId = i.UOMId 
    INNER JOIN coProductOldInformation j -- PK = CompanyCd, BFStatus, SpecCd
        ON a.CompanyCd = j.CompanyCd
            AND b.BFStatus = j.BFStatus
            AND b.ProductSalesCd = j.ProductSalesCd
    INNER JOIN coProductGroup1 g1 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup1Cd
        ON e.ProductGroup1Cd  = g1.ProductGroup1Cd
    INNER JOIN coProductGroup2 g2 -- PK = CompanyCd, ProductCategoryCd, UsedDepartment, ProductGroup2Cd
        ON e.ProductGroup1Cd  = g2.ProductGroup1Cd
Anônimo
fonte
1
Você projeta algum atributo coUOM? Caso contrário, você poderá usar uma semi-junção. Se sim, você seria capaz de usar UNIONcomo uma alternativa. A publicação apenas de sua FROMcláusula é uma informação inadequada aqui.
onedaywhen
1
Eu me pergunto isso com tanta frequência (porque eu vejo o tempo todo).
Paul Draper
1
Você perdeu um pedido em seu breve esquema? Recentemente, enfrentei um problema em que alterar uma INNER JOIN para LEFT OUTER JOIN acelera a consulta de 3 minutos para 10 segundos. Se você realmente tiver o Order By na sua consulta, explicarei mais como resposta. Parecia que todas as respostas realmente não explicavam o caso que eu enfrentei.
Phuah Yee Keat

Respostas:

403

A LEFT JOINnão é absolutamente mais rápido que um INNER JOIN. De fato, é mais lento; por definição, uma junção externa ( LEFT JOINou RIGHT JOIN) deve fazer todo o trabalho de um INNER JOINmais o trabalho extra de estender nulos os resultados. Também seria esperado que retornasse mais linhas, aumentando ainda mais o tempo total de execução simplesmente devido ao tamanho maior do conjunto de resultados.

(E mesmo que um LEFT JOIN fosse mais rápido em situações específicas devido a uma confluência de fatores difícil de imaginar, ele não é funcionalmente equivalente a um INNER JOIN, portanto, você não pode simplesmente substituir todas as instâncias de uma pela outra!)

Provavelmente, seus problemas de desempenho estão em outro lugar, como não ter uma chave candidata ou chave estrangeira indexada corretamente. 9 mesas é bastante para se juntar, então a desaceleração pode literalmente estar quase em qualquer lugar. Se você postar seu esquema, poderemos fornecer mais detalhes.


Editar:

Refletindo ainda mais sobre isso, pude pensar em uma circunstância sob a qual a LEFT JOINpode ser mais rápida que uma INNER JOIN, e é quando:

  • Algumas das tabelas são muito pequenas (digamos, com menos de 10 linhas);
  • As tabelas não possuem índices suficientes para cobrir a consulta.

Considere este exemplo:

CREATE TABLE #Test1
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test1 (ID, Name) VALUES (1, 'One')
INSERT #Test1 (ID, Name) VALUES (2, 'Two')
INSERT #Test1 (ID, Name) VALUES (3, 'Three')
INSERT #Test1 (ID, Name) VALUES (4, 'Four')
INSERT #Test1 (ID, Name) VALUES (5, 'Five')

CREATE TABLE #Test2
(
    ID int NOT NULL PRIMARY KEY,
    Name varchar(50) NOT NULL
)
INSERT #Test2 (ID, Name) VALUES (1, 'One')
INSERT #Test2 (ID, Name) VALUES (2, 'Two')
INSERT #Test2 (ID, Name) VALUES (3, 'Three')
INSERT #Test2 (ID, Name) VALUES (4, 'Four')
INSERT #Test2 (ID, Name) VALUES (5, 'Five')

SELECT *
FROM #Test1 t1
INNER JOIN #Test2 t2
ON t2.Name = t1.Name

SELECT *
FROM #Test1 t1
LEFT JOIN #Test2 t2
ON t2.Name = t1.Name

DROP TABLE #Test1
DROP TABLE #Test2

Se você executar isso e visualizar o plano de execução, verá que a INNER JOINconsulta realmente custa mais que a LEFT JOIN, porque atende aos dois critérios acima. Isso ocorre porque o SQL Server deseja fazer uma correspondência de hash para o INNER JOIN, mas faz loops aninhados para o LEFT JOIN; o primeiro é normalmente muito mais rápido, mas como o número de linhas é muito pequeno e não há índice a ser usado, a operação de hash acaba sendo a parte mais cara da consulta.

Você pode ver o mesmo efeito escrevendo um programa em sua linguagem de programação favorita para executar um grande número de pesquisas em uma lista com 5 elementos, em comparação a uma tabela de hash com 5 elementos. Devido ao tamanho, a versão da tabela de hash é realmente mais lenta. Mas aumente para 50 elementos ou 5000 elementos, e a versão da lista diminui para um rastreamento, porque é O (N) vs. O (1) para a hashtable.

Mas mude esta consulta para que esteja na IDcoluna e não, Namee você verá uma história muito diferente. Nesse caso, ele faz loops aninhados para as duas consultas, mas a INNER JOINversão é capaz de substituir uma das verificações de índice em cluster por uma busca - o que significa que isso literalmente será uma ordem de magnitude mais rápida com um grande número de linhas.

Portanto, a conclusão é mais ou menos o que mencionei vários parágrafos acima; isso é quase certamente um problema de indexação ou cobertura de índice, possivelmente combinado com uma ou mais tabelas muito pequenas. Essas são as únicas circunstâncias nas quais o SQL Server às vezes pode escolher um plano de execução pior para um INNER JOINque para um LEFT JOIN.

Aaronaught
fonte
4
Há outro cenário que pode levar a um OUTER JOIN com desempenho melhor que um INNER JOIN. Veja minha resposta abaixo.
dbenham
12
Quero ressaltar que basicamente não existe documentação do banco de dados para apoiar a idéia de que as junções internas e externas se saem de maneira diferente. Associações externas são um pouco mais caras que associações internas, devido ao volume dos dados e ao tamanho do conjunto de resultados. No entanto, os algoritmos subjacentes ( msdn.microsoft.com/en-us/library/ms191426(v=sql.105).aspx ) são os mesmos para os dois tipos de junções. O desempenho deve ser semelhante quando eles retornam quantidades semelhantes de dados.
Gordon Linoff
3
@Aaronaught. . . Essa resposta foi referenciada em um comentário que dizia algo no sentido de que "as junções externas têm desempenho significativamente pior que as junções internas". Comentei apenas para ter certeza de que essa má interpretação não se espalhou.
Gordon Linoff
16
Penso que esta resposta é enganosa em um aspecto importante: porque afirma "Uma junção esquerda não é absolutamente mais rápida que uma junção interna". Esta linha não está correta. É teoricamente não mais rápido do que um INNER JOIN. NÃO é "absolutamente não mais rápido". A questão é especificamente uma questão de desempenho. Na prática, já vi alguns sistemas (por empresas muito grandes!) Em que INNER JOIN era ridiculamente lento em comparação com OUTER JOIN. Teoria e prática são coisas muito diferentes.
David Frenkel
5
@ DavidFrenkel: Isso é altamente improvável. Eu pediria para ver uma comparação A / B, com planos de execução, se você acredita que essa discrepância é possível. Possivelmente, está relacionado a planos de consulta / execução em cache ou estatísticas incorretas.
Aaronaught
127

Há um cenário importante que pode fazer com que uma junção externa seja mais rápida que uma junção interna que ainda não foi discutida.

Ao usar uma junção externa, o otimizador sempre estará livre para eliminar a tabela de junção externa do plano de execução se as colunas de junção forem a PK da tabela externa e nenhuma das colunas da tabela externa for referenciada fora da própria junção externa. Por exemplo, SELECT A.* FROM A LEFT OUTER JOIN B ON A.KEY=B.KEYB.KEY é o PK de B. Tanto o Oracle (acredito que estava usando o release 10) quanto o Sql Server (usei 2008 R2) retiram a tabela B do plano de execução.

O mesmo não é necessariamente verdadeiro para uma junção interna: SELECT A.* FROM A INNER JOIN B ON A.KEY=B.KEYpode ou não exigir B ​​no plano de execução, dependendo de quais restrições existem.

Se A.KEY for uma chave estrangeira anulável que faça referência a B.KEY, o otimizador não poderá excluir B do plano porque deve confirmar que existe uma linha B para cada linha A.

Se A.KEY é uma chave estrangeira obrigatória que faz referência a B.KEY, o otimizador pode liberar B do plano porque as restrições garantem a existência da linha. Mas apenas porque o otimizador pode descartar a tabela do plano, não significa que sim. O SQL Server 2008 R2 NÃO elimina B do plano. O Oracle 10 exclui B do plano. É fácil ver como a associação externa superará a associação interna no SQL Server nesse caso.

Este é um exemplo trivial e não é prático para uma consulta independente. Por que participar de uma mesa se você não precisa?

Mas isso pode ser uma consideração de design muito importante ao projetar vistas. Freqüentemente é criada uma visualização "faça tudo" que une tudo o que um usuário pode precisar relacionado a uma tabela central. (Especialmente se houver usuários ingênuos fazendo consultas ad-hoc que não entendem o modelo relacional) A exibição pode incluir todas as colunas relevantes de muitas tabelas. Mas os usuários finais podem acessar apenas colunas de um subconjunto das tabelas na visualização. Se as tabelas forem unidas a junções externas, o otimizador poderá (e fará) eliminar as tabelas desnecessárias do plano.

É fundamental garantir que a exibição usando junções externas ofereça os resultados corretos. Como Aaronaught disse - você não pode substituir cegamente OUTER JOIN por INNER JOIN e esperar os mesmos resultados. Mas há momentos em que pode ser útil por razões de desempenho ao usar visualizações.

Uma última observação - não testei o impacto no desempenho à luz do exposto acima, mas, em teoria, parece que você poderá substituir com segurança uma INNER JOIN por uma OUTER JOIN se também adicionar a condição <FOREIGN_KEY> NÃO É NULL para a cláusula where.

dbenham
fonte
5
Na verdade, eu me deparei com esse problema ao criar consultas extremamente dinâmicas. Eu havia deixado em um INNER JOIN do qual estava usando e não obtendo dados, e quando o mudei para um LEFT JOIN (por curiosidade), a consulta realmente foi mais rápida.
Erik Philips
1
EDIT - Clarificou as condições que devem existir para o otimizador descartar a tabela de junção externa do plano de execução.
dbenham
2
Um pequeno esclarecimento para sua resposta: Quando a coluna da chave estrangeira não pode ser anulada, INNER JOIN e LEFT JOIN se tornam semanticamente equivalentes (ou seja, sua cláusula sugerida WHERE é redundante); a única diferença seria o plano de execução.
Douglas
2
Embora isso mostre um exemplo aparentemente trivial, essa é uma resposta extraordinariamente perspicaz!
Pbalaga 5/05
6
+1: eu pareço ter encontrado isso em algumas consultas em que estava usando junções internas com algumas tabelas muito grandes. A junção interna estava causando um derramamento no tempdb no plano de consulta (presumo que pelo motivo exposto acima - e meu servidor não possua memória RAM para armazenar tudo na memória). Mudar para junções à esquerda eliminou o derramamento para tempdb, o resultado é que algumas das minhas consultas de 20 a 30 segundos agora são executadas em frações de segundo. Essa é uma questão muito importante, pois a maioria das pessoas parece assumir que as junções internas são mais rápidas.
phosplait
23

Se tudo funciona como deveria, mas todos sabemos que tudo não funciona da maneira que deveria, especialmente quando se trata do otimizador de consultas, cache do plano de consulta e estatísticas.

Primeiro, sugiro reconstruir o índice e as estatísticas e, em seguida, limpar o cache do plano de consulta apenas para garantir que isso não esteja estragando tudo. No entanto, experimentei problemas mesmo quando isso é feito.

Eu experimentei alguns casos em que uma junção esquerda foi mais rápida que uma junção interna.

O motivo subjacente é o seguinte: se você possui duas tabelas e se junta a uma coluna com um índice (nas duas tabelas). A junção interna produzirá o mesmo resultado, independentemente se você fizer um loop sobre as entradas no índice na tabela um e corresponder ao índice na tabela dois como se você fizesse o inverso: Fazer loop sobre as entradas no índice na tabela dois e corresponder ao índice na tabela um. O problema é que, quando você tem estatísticas enganosas, o otimizador de consulta usa as estatísticas do índice para encontrar a tabela com menos entradas correspondentes (com base em seus outros critérios). Se você possui duas tabelas com 1 milhão em cada uma, na tabela 1 você tem 10 linhas correspondentes e na tabela 2 você tem 100000 linhas correspondentes. A melhor maneira seria fazer uma varredura de índice na tabela um e corresponder 10 vezes na tabela dois. O inverso seria uma varredura de índice que repetisse mais de 100000 linhas e tentasse corresponder 100000 vezes e apenas 10 fossem bem-sucedidas. Portanto, se as estatísticas não estiverem corretas, o otimizador poderá escolher a tabela e o índice incorretos a serem repetidos.

Se o otimizador optar por otimizar a junção esquerda na ordem em que foi gravado, ele terá um desempenho melhor que a junção interna.

MAS, o otimizador também pode otimizar uma junção esquerda sub-idealmente como uma semi-junção esquerda. Para escolher a que você deseja, use a dica de ordem de força.

Kvasi
fonte
18

Tente as duas consultas (aquela com junção interna e esquerda) OPTION (FORCE ORDER)no final e publique os resultados. OPTION (FORCE ORDER)é uma dica de consulta que força o otimizador a criar o plano de execução com a ordem de associação que você forneceu na consulta.

Se INNER JOINcomeça a funcionar tão rápido quanto LEFT JOIN, é porque:

  • Em uma consulta composta inteiramente por INNER JOINs, a ordem de junção não importa. Isso permite que o otimizador de consultas solicite as junções como achar melhor, para que o problema possa depender do otimizador.
  • Com LEFT JOIN, não é esse o caso, pois alterar a ordem de junção alterará os resultados da consulta. Isso significa que o mecanismo deve seguir a ordem de junção que você forneceu na consulta, que pode ser melhor que a otimizada.

Não sei se isso responde à sua pergunta, mas eu já participei de um projeto que apresentava consultas altamente complexas fazendo cálculos, o que atrapalhava completamente o otimizador. Tivemos casos em FORCE ORDERque a reduziria o tempo de execução de uma consulta de 5 minutos para 10 segundos.

Francisco Pires
fonte
9

Fizeram várias comparações entre as junções externa e interna esquerda e não foram capazes de encontrar uma diferença consistente. Existem muitas variáveis. Estou trabalhando em um banco de dados de relatórios com milhares de tabelas, muitas com um grande número de campos, muitas alterações ao longo do tempo (versões de fornecedores e fluxo de trabalho local). Não é possível criar todas as combinações de índices de cobertura para atender às necessidades de uma ampla variedade de consultas e manipular dados históricos. As consultas internas viram o desempenho do servidor prejudicar porque duas tabelas grandes (milhões a dezenas de milhões de linhas) são unidas internamente, puxando um grande número de campos e não existe um índice de cobertura.

O maior problema, no entanto, não parece mais favorável nas discussões acima. Talvez seu banco de dados seja bem projetado com gatilhos e processamento de transações bem projetado para garantir bons dados. Os meus freqüentemente têm valores NULL onde eles não são esperados. Sim, as definições da tabela podem impor nulos, mas isso não é uma opção no meu ambiente.

Portanto, a questão é ... você projeta sua consulta apenas para velocidade, uma prioridade mais alta para o processamento de transações que executa o mesmo código milhares de vezes por minuto. Ou você busca a precisão que uma junção externa esquerda fornecerá. Lembre-se de que as junções internas devem encontrar correspondências nos dois lados; portanto, um NULL inesperado não apenas removerá os dados das duas tabelas, mas possivelmente linhas inteiras de informações. E isso acontece tão bem, sem mensagens de erro.

Você pode ser muito rápido ao obter 90% dos dados necessários e não descobrir que as junções internas removeram silenciosamente as informações. Às vezes, as junções internas podem ser mais rápidas, mas não acredito que alguém faça essa suposição, a menos que tenha revisado o plano de execução. A velocidade é importante, mas a precisão é mais importante.

JO
fonte
8

É mais provável que seus problemas de desempenho sejam causados ​​pelo número de associações que você está fazendo e se as colunas nas quais você está ingressando têm índices ou não.

Na pior das hipóteses, você poderia facilmente fazer 9 varreduras inteiras na tabela para cada associação.

eddiegroves
fonte
7

As junções externas podem oferecer desempenho superior quando usadas em visualizações.

Digamos que você tenha uma consulta que envolva uma exibição, e essa exibição seja composta por 10 tabelas unidas. Digamos que sua consulta use apenas colunas de 3 dessas 10 tabelas.

Se essas 10 tabelas tivessem sido unidas internamente, o otimizador de consulta precisaria juntar todas, mesmo que sua consulta em si não precise de 7 das 10 tabelas. Isso ocorre porque as junções internas podem filtrar os dados, tornando-os essenciais para a computação.

Se essas 10 tabelas tivessem sido unidas externamente, o otimizador de consulta apenas se uniria às necessárias: três em cada dez delas, neste caso. Isso ocorre porque as próprias junções não estão mais filtrando os dados e, portanto, as junções não utilizadas podem ser puladas.

Fonte: http://www.sqlservercentral.com/blogs/sql_coach/2010/07/29/poor-little-misunderstood-views/

MarredCheese
fonte
1
Sua declaração sobre "junção externa" é enganosa e potencialmente incorreta. Externo significa que os dados do outro lado não precisam existir - e se não substituírem NULL. Sob circunstâncias específicas, o RDBMS pode "ignorá-los" (consulte a resposta acima do dbenham). NO ENTANTO - externo versus interno pode fazer com que sua consulta retorne resultados radicalmente diferentes. INNER significa - forneça resultados para os quais um item está em AMBOS A e B. ESQUERDO EXTERNO significa todos de A e, opcionalmente, B, se existir. Primeiro caso - você obtém algumas linhas; no segundo, obtém TODAS as linhas.
ripvlan
1
@ripvlan Obviamente, as junções externas e internas nem sempre são intercambiáveis. A pergunta original era sobre desempenho, o que implica que estamos falando de casos em que uma das junções retornaria o mesmo conjunto de resultados.
MarshCheese # 9/17
1
Sim e - o EXTERIOR pode causar um problema de desempenho porque fará com que todas as linhas (mais dados) sejam retornadas. Sua suposição de que as consultas resultam na mesma saída é razoável - no entanto, não é verdade no caso geral e específica para cada design de banco de dados. E para aqueles que não estão 100% familiarizados com álgebra relacional, podem causar sofrimento. O que quero dizer é apenas oferecer mais informações às pessoas que estão lendo isso procurando conselhos e que uma ESQUERDA / DIREITA não resolva magicamente um problema e pode causar mais problemas. É um poder deixado para o nível de 300 :-)
ripvlan
2

Encontrei algo interessante no SQL Server ao verificar se as junções internas são mais rápidas que as esquerdas.

Se você não incluir os itens da tabela de junção esquerda, na instrução select, a junção esquerda será mais rápida que a mesma consulta com junção interna.

Se você incluir a tabela de junção esquerda na instrução select, a junção interna com a mesma consulta foi igual ou mais rápida que a junção esquerda.

Buzzzzzzz
fonte
0

Pelas minhas comparações, acho que eles têm exatamente o mesmo plano de execução. Existem três cenários:

  1. Se e quando eles retornarem os mesmos resultados, eles terão a mesma velocidade. No entanto, devemos ter em mente que elas não são as mesmas consultas e que LEFT JOIN possivelmente retornará mais resultados (quando algumas condições ON não forem atendidas) - é por isso que geralmente é mais lento.

  2. Quando a tabela principal (a primeira não-const no plano de execução) possui uma condição restritiva (WHERE id =?) E a condição ON correspondente está em um valor NULL, a tabela "direita" não é unida --- é quando LEFT JOIN é mais rápido.

  3. Conforme discutido no ponto 1, geralmente INNER JOIN é mais restritivo e retorna menos resultados e, portanto, é mais rápido.

Ambos usam índices (os mesmos).

Jiulin Teng
fonte