O agrupamento da consulta em IF EXISTS torna muito lento

16

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 EXISTSaltera 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:

  1. Crie tabela e script estatístico para obter o mesmo plano que o meu
  2. Plano de execução lenta
  3. 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

TheGameiswar
fonte
A discussão sobre esta questão foi movida para esta sala de bate-papo .
Paul White Reinstate Monica

Respostas:

18

Como tem sido explicado por Paul White em seu blog: Dentro do Optimizer: Linha Gols em profundidade os EXISTSintroduz uma meta linha, que prefere NESTED LOOPSou MERGE JOINmaisHASH MATCH

Como exemplo final, considere que uma semi-junção lógica (como uma subconsulta introduzida com EXISTS) compartilha o tema geral: deve ser otimizada para encontrar a primeira linha correspondente rapidamente.

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 EXISTSda sua consulta.

Você pode reescrever sua consulta usando LEFT OUTER JOINae verificar se não havia uma linha na tabela pequena testandoNULL

If EXISTS(
    SELECT databasename
    FROM somedb.dbo.bigtable l
    LEFT JOIN dbo.smalltable c ON c.source = l.source
    WHERE databasename = 'someval'
    AND source <> 'kt'
    AND c.source IS NULL
)

Você provavelmente também poderia usar uma EXCEPTconsulta, dependendo de quantos campos você precisa comparar assim:

If EXISTS(
   SELECT source
   FROM somedb.dbo.bigtable l
   WHERE databasename = 'someval'
   AND source <> 'kt'

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)

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

Tom V - Equipe Monica
fonte
0

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.

If not exists(
    select databasename 
    from somedb.dbo.bigtable l
    inner hash join dbo.smalltable c 
        on c.source = l.source
where databasename ='someval' and source  <>'kt')
begin
    Raiserror('Source missing',16,1)
    Return
end

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

Artem Machnev
fonte
Do que você, vai testá-lo
TheGameiswar
0

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:

IF
(
    SELECT
        COUNT(l.databasename) + 1 AS databasename
    FROM somedb.dbo.bigtable AS l

    WHERE   l.databasename ='someval'
        AND l.[source]  <> 'kt'
        AND NOT EXISTS(SELECT 1 FROM dbo.smalltable AS c WHERE c.[source]=l.[source])
) > 1 --Acts like EXISTS
BEGIN
    RAISERROR('Source missing', 16, 1)
RETURN
END

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!

Hayder Nahee
fonte