WENN EXISTS länger dauert als die eingebettete select-Anweisung

35

Wenn ich den folgenden Code ausführe, dauert es 22,5 Minuten und liest 106 Millionen. Wenn ich jedoch nur die innere select-Anweisung selbst ausführe, dauert es nur 15 Sekunden und 264k liest. Als Randnotiz gibt die Auswahlabfrage keine Datensätze zurück.

Irgendeine Idee, warum das IF EXISTSso viel länger laufen und so viel mehr liest? Ich habe auch die Select-Anweisung geändert SELECT TOP 1 [dlc].[id]und sie nach 2 Minuten getötet.

Als vorübergehende Korrektur habe ich es geändert, um eine Zählung (*) durchzuführen und diesen Wert einer Variablen zuzuweisen @cnt. Dann macht es eine IF 0 <> @cntAussage. Aber ich dachte, es EXISTSwäre besser, denn wenn Datensätze in der select-Anweisung zurückgegeben würden, würde die Suche abgebrochen, sobald mindestens ein Datensatz gefunden wurde, während count(*)die vollständige Abfrage abgeschlossen wird. Was vermisse ich?

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
Chris Woods
quelle
4
Um das Zeilenzielproblem zu vermeiden, könnte eine andere Idee (ungetestet, wohlgemerkt!) Darin bestehen, das Inverse - zu versuchen IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Aaron Bertrand

Antworten:

32

Irgendeine Idee, warum das IF EXISTSso viel länger laufen und so viel mehr liest? Ich habe auch die Select-Anweisung geändert SELECT TOP 1 [dlc].[id]und sie nach 2 Minuten getötet.

Wie ich in meiner Antwort auf diese verwandte Frage erklärte:

Wie (und warum) wirkt sich TOP auf einen Ausführungsplan aus?

Using EXISTSführt ein Zeilenziel ein, bei dem das Optimierungsprogramm einen Ausführungsplan erstellt, mit dem die erste Zeile schnell lokalisiert werden soll. Dabei wird davon ausgegangen, dass die Daten gleichmäßig verteilt sind. Wenn Statistiken beispielsweise zeigen, dass 100 erwartete Übereinstimmungen in 100.000 Zeilen vorliegen, wird davon ausgegangen, dass nur 1.000 Zeilen gelesen werden müssen, um die erste Übereinstimmung zu finden.

Dies führt zu längeren Ausführungszeiten als erwartet, wenn sich diese Annahme als fehlerhaft herausstellt. Wenn SQL Server beispielsweise eine Zugriffsmethode (z. B. einen ungeordneten Scan) auswählt, bei der der erste übereinstimmende Wert sehr spät in der Suche gefunden wird, kann dies zu einem fast vollständigen Scan führen. Wenn andererseits eine übereinstimmende Zeile in den ersten Zeilen gefunden wird, ist die Leistung sehr gut. Dies ist das grundlegende Risiko bei Zeilenzielen - inkonsistente Leistung.

Als vorübergehende Korrektur habe ich es geändert, um eine Zählung (*) durchzuführen und diesen Wert einer Variablen zuzuweisen

Es ist normalerweise möglich, die Abfrage so umzuformulieren, dass kein Zeilenziel zugewiesen wird. Ohne das Zeilenziel kann die Abfrage immer noch beendet werden, wenn die erste übereinstimmende Zeile gefunden wird (wenn sie korrekt geschrieben wurde), aber die Strategie des Ausführungsplans ist wahrscheinlich anders (und hoffentlich effektiver). Offensichtlich erfordert count (*) das Lesen aller Zeilen, daher ist es keine perfekte Alternative.

Wenn Sie SQL Server 2008 R2 oder höher ausführen , können Sie im Allgemeinen auch das dokumentierte und unterstützte Ablaufverfolgungsflag 4138 verwenden , um einen Ausführungsplan ohne Zeilenziel abzurufen. Dieses Flag kann auch mithilfe des unterstützten Hinweises angegeben OPTION (QUERYTRACEON 4138)werden. Beachten Sie jedoch , dass dafür die Berechtigung sysadmin zur Laufzeit erforderlich ist , sofern dies nicht zusammen mit einem Planungshandbuch verwendet wird.

Unglücklicherweise

Keines der oben genannten ist mit einer IF EXISTSbedingten Anweisung funktionsfähig . Dies gilt nur für reguläre DML. Es wird mit der alternativen arbeiten SELECT TOP (1)Sie versucht Formulierung. Das kann durchaus besser sein als die Verwendung COUNT(*), bei der, wie bereits erwähnt, alle qualifizierten Zeilen gezählt werden müssen.

Es gibt jedoch eine Reihe von Möglichkeiten, diese Anforderung auszudrücken, mit denen Sie das Zeilenziel vermeiden oder steuern können, während Sie die Suche vorzeitig beenden. Ein letztes Beispiel:

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;
Paul White sagt GoFundMonica
quelle
Das von Ihnen angegebene alternative Beispiel lief in 3,75 Minuten und führte 46-Meter-Lesevorgänge durch. Obwohl ich schneller als meine ursprüngliche Abfrage bin, denke ich, dass ich in diesem Fall bei @cnt = count (*) bleiben und die Variable anschließend auswerten werde. Zumal in 99% der Fälle nichts drin ist. Basierend auf den Antworten von Ihnen und Rob klingt es so, als ob Exists nur dann gut ist, wenn Sie wirklich ein Ergebnis erwarten und dieses Ergebnis gleichmäßig in Ihren Daten verteilt ist.
Chris Woods
3
@ChrisWoods: Sie sagten "Zumal in 99% der Fälle nichts drin ist". Dies garantiert ziemlich genau, dass das Zeilenziel von one eine schlechte Idee ist, da Sie erwarten, dass es normalerweise KEINE Zeilen gibt und Sie alles scannen müssen, um festzustellen, dass es keine gibt. Wenn Sie keinen cleveren Index hinzufügen können, bleiben Sie bei COUNT (*).
Ross Presser
25

Da EXISTS nur eine einzelne Zeile finden muss, wird ein Zeilenziel von eins verwendet. Dies kann manchmal zu einem weniger als idealen Plan führen. Wenn Sie erwarten, dass dies für Sie so ist, füllen Sie eine Variable mit dem Ergebnis von a COUNT(*)und testen Sie diese Variable, um festzustellen, ob sie größer als 0 ist.

Also ... Mit einem kleinen Zeilenziel wird vermieden, dass Vorgänge blockiert werden, z. B. das Erstellen von Hashtabellen oder das Sortieren von Flüssen, die für Zusammenführungsverknüpfungen nützlich sein könnten, da sich herausstellt, dass etwas schnell gefunden werden muss und daher geschachtelte Schleifen erforderlich sind sei am besten wenn es etwas findet. Abgesehen davon, dass dies einen Plan erschweren kann, der über den gesamten Satz viel schlimmer ist. Wenn es schnell gehen würde, eine einzelne Zeile zu finden, würden Sie diese Methode gerne verwenden, um Blockierungen zu vermeiden ...

Rob Farley
quelle