it-swarm.com.de

WENN EXISTS länger dauert als eingebettete select-Anweisung

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

Irgendeine Idee, warum der IF EXISTS Es so viel länger laufen lassen und so viel mehr Lesevorgänge machen würde? Ich habe auch die select-Anweisung in SELECT TOP 1 [dlc].[id] Geändert und sie nach 2 Minuten beendet.

Als vorübergehende Korrektur habe ich es geändert, um eine Zählung (*) durchzuführen und diesen Wert einer Variablen @cnt Zuzuweisen. Dann wird eine IF 0 <> @cnt - Anweisung ausgeführt. Aber ich dachte, EXISTS wäre besser, denn wenn Datensätze in der select-Anweisung zurückgegeben würden, würde der Scan/Suchvorgang beendet, sobald mindestens ein Datensatz gefunden wurde, während count(*) abgeschlossen wird die vollständige Abfrage. 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
35
Chris Woods

Irgendeine Idee, warum der IF EXISTS Es so viel länger laufen lassen und so viel mehr Lesevorgänge machen würde? Ich habe auch die select-Anweisung in SELECT TOP 1 [dlc].[id] Geändert und sie nach 2 Minuten beendet.

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

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

Mit EXISTS wird ein Zeilenziel eingeführt, bei dem der Optimierer einen Ausführungsplan erstellt, mit dem die erste Zeile schnell gefunden werden soll. Dabei wird davon ausgegangen, dass die Daten gleichmäßig verteilt sind. Wenn Statistiken beispielsweise zeigen, dass in 100.000 Zeilen 100 Übereinstimmungen erwartet werden, 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 auswählt (z. B. einen ungeordneten Scan), 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 richtig geschrieben ist), aber die Ausführungsplanstrategie ist wahrscheinlich anders (und hoffentlich effektiver). Für count (*) müssen natürlich alle Zeilen gelesen werden, daher ist dies keine perfekte Alternative.

Wenn Sie SQL Server 2008 R2 oder höher ausführen, können Sie im Allgemeinen auch dokumentiertes und unterstütztes Trace-Flag 4138 verwenden, um einen Ausführungsplan ohne Zeilenziel abzurufen. Dieses Flag kann auch mit nterstützter HinweisOPTION (QUERYTRACEON 4138) angegeben werden. Beachten Sie jedoch, dass Laufzeit sysadmin erforderlich ist. Erlaubnis, sofern nicht mit einem Planführer verwendet.

Unglücklicherweise

Keine der oben genannten Funktionen funktioniert mit einer bedingten Anweisung IF EXISTS. Dies gilt nur für reguläre DML. Es funktioniert mit der alternativen SELECT TOP (1) - Formulierung, die Sie ausprobiert haben. Das ist vielleicht besser als die Verwendung von COUNT(*), die, wie bereits erwähnt, alle qualifizierten Zeilen zählen muss.

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;
32
Paul White 9

Da EXISTS nur eine einzelne Zeile finden muss, wird ein Zeilenziel von eins verwendet. Dies kann manchmal zu einem nicht idealen Plan führen. Wenn Sie erwarten, dass dies für Sie so ist, füllen Sie eine Variable mit dem Ergebnis einer 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 Hash-Tabellen oder das Sortieren von Flows, die für Zusammenführungsverknüpfungen nützlich sein könnten, da sich herausstellt, dass sie schnell etwas finden müssen und daher verschachtelte Schleifen sei am besten, wenn es etwas gefunden hat. Abgesehen davon, dass dies einen Plan ergeben kann, der im gesamten Set viel schlechter ist. Wenn Sie schnell nach einer einzelnen Zeile suchen, möchten Sie diese Methode, um Blöcke zu vermeiden ...

25
Rob Farley