SE ESISTE impiega più tempo dell'istruzione select incorporata


35

Quando eseguo il seguente codice ci vogliono 22,5 minuti e fa 106 milioni di letture. Tuttavia, se eseguo solo l'istruzione select interna da sola, ci vogliono solo 15 secondi e fa 264k letture. Come nota a margine, la query di selezione non restituisce alcun record.

Qualche idea sul perché lo IF EXISTSfarebbe funzionare così a lungo e fare così tante altre letture? Ho anche cambiato l'istruzione select da fare SELECT TOP 1 [dlc].[id]e l'ho uccisa dopo 2 minuti.

Come soluzione temporanea l'ho modificato per fare un conteggio (*) e assegnare quel valore a una variabile @cnt. Quindi fa una IF 0 <> @cntdichiarazione. Ma ho pensato che EXISTSsarebbe meglio, perché se ci fossero dei record restituiti nell'istruzione select, smetterebbe di eseguire la scansione / ricerca una volta trovato almeno un record, mentre il count(*)completamento della query completa. Cosa mi sto perdendo?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
Per evitare il problema dell'obiettivo di fila, un'altra idea (non testata, intendiamoci!) Potrebbe essere quella di provare l'inverso - IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Aaron Bertrand

Risposte:


32

Qualche idea sul perché lo IF EXISTSfarebbe funzionare così a lungo e fare così tante altre letture? Ho anche cambiato l'istruzione select da fare SELECT TOP 1 [dlc].[id]e l'ho uccisa dopo 2 minuti.

Come ho spiegato nella mia risposta a questa domanda correlata:

In che modo (e perché) TOP influisce su un piano di esecuzione?

L'utilizzo EXISTSintroduce un obiettivo di riga, in cui l'ottimizzatore produce un piano di esecuzione volto a individuare rapidamente la prima riga. Nel fare ciò, si presuppone che i dati siano distribuiti uniformemente. Ad esempio, se le statistiche mostrano che ci sono 100 partite attese in 100.000 righe, supporrà che dovrà leggere solo 1.000 righe per trovare la prima partita.

Ciò comporterà tempi di esecuzione più lunghi del previsto se questo presupposto risulta essere difettoso. Ad esempio, se SQL Server sceglie un metodo di accesso (ad es. Scansione non ordinata) per individuare il primo valore corrispondente molto tardi nella ricerca, potrebbe risultare in una scansione quasi completa. D'altra parte, se tra le prime righe si trova una riga corrispondente, le prestazioni saranno molto buone. Questo è il rischio fondamentale con obiettivi di fila: prestazioni incoerenti.

Come soluzione temporanea l'ho modificato per fare un conteggio (*) e assegnare quel valore a una variabile

In genere è possibile riformulare la query in modo che non venga assegnato un obiettivo di riga. Senza l'obiettivo di riga, la query può comunque terminare quando viene rilevata la prima riga corrispondente (se scritta correttamente), ma è probabile che la strategia del piano di esecuzione sia diversa (e si spera, più efficace). Ovviamente, count (*) richiederà la lettura di tutte le righe, quindi non è un'alternativa perfetta.

Se si esegue SQL Server 2008 R2 o versioni successive, in genere è anche possibile utilizzare il flag di traccia 4138 documentato e supportato per ottenere un piano di esecuzione senza un obiettivo di riga. Questo flag può anche essere specificato utilizzando l' hint supportato OPTION (QUERYTRACEON 4138) , sebbene si tenga presente che richiede l' autorizzazione sysadmin di runtime , a meno che non venga utilizzato con una guida di piano.

Sfortunatamente

Nessuna delle precedenti è funzionale con un'istruzione IF EXISTScondizionale. Si applica solo al normale DML. Esso sarà lavorare con il alternativo SELECT TOP (1)formulazione si è tentato. Potrebbe essere meglio dell'uso COUNT(*), che deve contare tutte le righe qualificate, come menzionato in precedenza.

Detto questo, ci sono molti modi per esprimere questo requisito che ti consentiranno di evitare o controllare l'obiettivo della riga, terminando la ricerca in anticipo. Un ultimo esempio:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

L'esempio alternativo che hai fornito è durato 3,75 minuti ed eseguito 46 milioni di letture. Quindi, sebbene più veloce della mia query originale, penso che in questo caso rimarrò con @cnt = count (*) e valuterò successivamente la variabile. Soprattutto dal 99% delle volte che funziona, non ci sarà nulla al suo interno. Sembra basato sulle risposte di Rob e di te che Exists sia buono solo se davvero ti aspetti una sorta di risultato e quel risultato è distribuito uniformemente nei tuoi dati.
Chris Woods,

3
@ChrisWoods: Hai detto "Soprattutto dal 99% delle volte che questo corre non ci sarà nulla". Questo praticamente garantisce che l'obiettivo di una riga sia una cattiva idea, dal momento che ti aspetti che di solito NON ci siano righe e che devi scansionare tutto per scoprire che non ce ne sono. Se non riesci ad aggiungere un indice intelligente, mantieni COUNT (*).
Ross Presser,

25

Poiché EXISTS deve solo trovare una singola riga, utilizzerà un obiettivo di una riga. Questo a volte può produrre un piano tutt'altro che ideale. Se ti aspetti che sia così per te, compila una variabile con il risultato di a COUNT(*)e quindi testa quella variabile per vedere se è maggiore di 0.

Quindi ... Con un obiettivo di piccola riga eviterà di bloccare le operazioni, come la creazione di tabelle hash o l'ordinamento di flussi che potrebbero essere utili per unire join, perché immaginerà che è destinato a trovare qualcosa abbastanza rapidamente, e quindi i cicli nidificati sii il migliore se trovasse qualcosa. Solo che questo può rendere un piano che è molto peggio in tutto il set. Se trovare una singola riga fosse veloce, ti piacerebbe questo metodo per evitare blocchi ...

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.