Eu tenho a consulta abaixo:
select databasename
from somedb.dbo.bigtable l where databasename ='someval' and source <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
A consulta acima é concluída em três segundos.
Se a consulta acima retornar algum valor, queremos que o procedimento armazenado saia, então eu o reescrevi como abaixo:
If Exists(
select databasename
from somedb.dbo.bigtable l where databasename ='someval' and source <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End
No entanto, isso leva 10 minutos.
Posso reescrever a consulta acima, como abaixo, que também é concluída em menos de 3 segundos:
select databasename
from somedb.dbo.bigtable l where databasename ='someval' and source <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End
O problema com a reescrita acima é que a consulta acima faz parte de um procedimento armazenado maior e retorna vários conjuntos de resultados. Em C #, iteramos em cada conjunto de resultados e fazemos algum processamento.
O exemplo acima retorna um conjunto de resultados vazio; portanto, se eu seguir essa abordagem, tenho que alterar meu C # e fazer a implantação novamente.
Então, minha pergunta é:
por que usar apenas
IF EXISTS
altera o plano para levar tanto tempo?
Abaixo estão os detalhes que podem ajudá-lo e deixe-me saber se você precisar de mais detalhes:
- Crie tabela e script estatístico para obter o mesmo plano que o meu
- Plano de execução lenta
Plano de execução rápida
Plano lento usando Brentozar Cole o plano
Plano rápido usando Brentozar Cole o plano
Nota: Ambas as consultas são as mesmas (usando parâmetros), a única diferença é EXISTS
(eu posso ter cometido alguns erros ao anonimizar).
Os scripts de criação de tabela estão abaixo:
http://pastebin.com/CgSHeqXc - estatísticas da tabela pequena
http://pastebin.com/GUu9KfpS - estatísticas da tabela grande
fonte
Respostas:
Como tem sido explicado por Paul White em seu blog: Dentro do Optimizer: Linha Gols em profundidade os
EXISTS
introduz uma meta linha, que prefereNESTED LOOPS
ouMERGE JOIN
maisHASH MATCH
Na sua consulta, isso aparentemente introduz loops aninhados e remove o paralelismo, resultando em um plano mais lento.
Portanto, você provavelmente precisaria encontrar uma maneira de reescrever sua consulta sem usar o
NOT EXISTS
da sua consulta.Você pode reescrever sua consulta usando
LEFT OUTER JOIN
ae verificar se não havia uma linha na tabela pequena testandoNULL
Você provavelmente também poderia usar uma
EXCEPT
consulta, dependendo de quantos campos você precisa comparar assim:Lembre-se, Aaron Bertrand tem uma postagem no blog fornecendo razões pelas quais ele prefere NOT EXISTS, que você deve ler para ver se outras abordagens funcionam melhor e para estar ciente dos possíveis problemas de correção em caso de valores NULL.
Perguntas e respostas relacionadas: SE EXISTE demorar mais do que a instrução select incorporada
fonte
Você precisa reescrever sua consulta usando junções explícitas e especificar qual operação de junção você deseja usar (loop, hash ou mesclagem) assim.
Ao usar EXISTS ou NOT EXISTS, o SQL Server gerou um plano de consulta com a operação NESTED LOOP, assumindo que ele deveria passar por todas as linhas do conjunto, uma por uma, procurando a primeira linha para satisfazer a condição. O uso do HASH JOIN acelerará.
fonte
Eu me deparei com o mesmo problema, consegui me contornar evitando o uso de "EXISTS" e usando a função "COUNT ()" e a instrução "IF ... ELSE".
Para o seu exemplo, tente o seguinte:
A razão pela qual estou adicionando "+ 1" à contagem é para que eu possa usar "> 1" na condição SE, usando "> 0" ou "<> 0" acionará a consulta para usar loops aninhados em vez de HASH Combine. Não investigamos por que isso está acontecendo exatamente seria interessante descobrir o porquê.
Espero que ajude!
fonte