Il wrapping della query in IF EXISTS lo rende molto lento


16

Ho la seguente domanda:

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)

La query precedente viene completata in tre secondi.

Se la query sopra restituisce un valore, vogliamo che la procedura memorizzata ESCI, quindi la riscrivo come di seguito:

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

Tuttavia, ci vogliono 10 minuti.

Posso riscrivere la query precedente come di seguito, che si completa anche in meno di 3 secondi:

  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

Il problema con la riscrittura sopra è che la query sopra fa parte di una procedura memorizzata più grande e restituisce più set di risultati. In C #, passiamo in rassegna ogni set di risultati e eseguiamo alcune elaborazioni.

Quanto sopra restituisce un set di risultati vuoto, quindi se seguo questo approccio, devo cambiare il mio C # ed eseguire di nuovo la distribuzione.

Quindi la mia domanda è

perché usare semplicemente IF EXISTScambia il piano per richiedere così tanto tempo?

Di seguito sono riportati i dettagli che potrebbero esserti utili e fammi sapere se hai bisogno di ulteriori dettagli:

  1. Crea uno script di tabelle e statistiche per ottenere lo stesso piano del mio
  2. Piano di esecuzione lento
  3. Piano di esecuzione rapida

    Piano lento usando Brentozar Incolla il piano
    Piano veloce usando Brentozar Incolla il piano

Nota: entrambe le query sono uguali (utilizzando i parametri), l'unica differenza è EXISTS(potrei aver commesso degli errori durante l'anonimizzazione).

Gli script di creazione della tabella sono di seguito:

http://pastebin.com/CgSHeqXc - statistiche per piccoli tavoli
http://pastebin.com/GUu9KfpS - statistiche per grandi tavoli


La discussione su questa domanda è stata spostata in questa chat room .
Paul White Ripristina Monica

Risposte:


18

Come è stato spiegato da Paul White nel suo post sul blog: All'interno del Optimizer: Obiettivi di riga in profondità le EXISTSintroduce un obiettivo fila, che preferisce NESTED LOOPSo MERGE JOINsopraHASH MATCH

Come ultimo esempio, considera che un semi-join logico (come una sottoquery introdotta con EXISTS) condivide il tema generale: dovrebbe essere ottimizzato per trovare rapidamente la prima riga corrispondente.

Nella tua query questo sembra apparentemente introdurre cicli nidificati e rimuovere il parallelismo, risultando in un piano più lento.

Quindi probabilmente avresti bisogno di trovare un modo per riscrivere la tua query senza usare il NOT EXISTSdalla tua query.

Potresti LEFT OUTER JOINcavartela riscrivendo la tua query usando a e controllando che non ci fosse una riga in smalltable 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
)

Probabilmente potresti anche usare una EXCEPTquery, a seconda di quanti campi devi confrontare in questo modo:

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

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)

Intendiamoci, Aaron Bertrand ha un post sul blog che fornisce i motivi per cui preferisce NOT EXISTS che dovresti leggere per vedere se altri approcci funzionano meglio e per essere consapevoli dei potenziali problemi di correttezza in caso di valori NULL.

Domande e risposte correlate: SE ESISTE richiede più tempo dell'istruzione di selezione incorporata


0

È necessario riscrivere la query utilizzando join espliciti e specificare quale operazione di join si desidera utilizzare (loop, hash o unione) in questo modo.

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

Quando si utilizza EXISTS o NOT EXISTS, SQL Server ha generato un piano di query con l'operazione NESTED LOOP supponendo che dovrebbe andare su tutte le righe nel set una alla volta cercando la prima riga per soddisfare la condizione. L'uso di HASH JOIN lo accelererà.


Di te lo
proverai

0

Ho riscontrato lo stesso problema, sono riuscito a ovviare a me stesso evitando di usare "EXISTS" e facendo uso della funzione "COUNT ()" e dell'istruzione "IF ... ELSE".

Per il tuo esempio prova quanto segue:

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

Il motivo per cui sto aggiungendo "+ 1" al conteggio è che posso usare "> 1" nella condizione IF, usando "> 0" o "<> 0" attiverà la query per usare loop nidificati invece di HASH Incontro. Non ho studiato il motivo per cui ciò che sta accadendo sarebbe interessante scoprire perché.

Spero possa aiutare!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.