Warum bewertet SQL eine WHERE-Klausel, die False ist?

8

Ich habe eine Abfrage in SQL (2008), die ich nicht verstehen kann, warum es viel länger dauert, um zu bewerten, wenn ich eine Klausel in eine WHERE-Anweisung aufnehmen, die das Ergebnis nicht beeinflussen sollte. Hier ist ein Beispiel für die Abfrage:

%Vor%

Offensichtlich wird @includeAll = 1 in diesem Fall falsch ausgewertet; Durch das Hinzufügen wird jedoch die Zeit der Abfrage erhöht, als ob sie immer wahr wäre. Das Ergebnis, das ich bekomme, ist korrekt mit oder ohne diese Klausel: Ich bekomme nur den 1-Eintrag mit Id = 3926, aber (in meiner realen Abfrage) einschließlich dieser Zeile erhöht die Abfragezeit von & lt; 0 Sekunden bis etwa 7 Minuten ... so scheint es, dass es die Abfrage ausführt, als ob die Aussage wahr wäre, obwohl es nicht ist, aber immer noch die richtigen Ergebnisse liefert.

Jedes Licht, das sich über den Grund aussagen lässt, wäre hilfreich. Außerdem, wenn Sie einen Vorschlag haben, um es zu umgehen, würde ich es nehmen. Ich möchte eine Klausel wie diese haben, damit ich einen Parameter in einer gespeicherten Prozedur einschließen kann, die es die Id ignorieren lässt, die es hat, und alle Ergebnisse zurückgeben, wenn es auf wahr gesetzt wird, aber ich kann nicht zulassen, dass das die Auswirkung beeinflusst Leistung, wenn Sie einfach versuchen, einen Datensatz zu bekommen.

    
waltsj19 19.05.2014, 15:58
quelle

6 Antworten

6

Um sicher zu sein, müssen Sie sich den Abfrageplan ansehen, aber wenn Sie OR verwenden, wird dies in manchen DBMS oft so aussehen.

Lies auch @Bogdan Sahles Antwort auf einige großartige Details, warum das passiert.

Dies funktioniert möglicherweise nicht, aber Sie können etwas versuchen, wenn Sie gerade mit SQL arbeiten müssen:

%Vor%

Wenn Sie eine Stored Procedure verwenden, können Sie das SQL in beiden Fällen bedingt ausführen, was wahrscheinlich effizienter ist.

Etwas wie:

%Vor%     
woot 19.05.2014, 16:09
quelle
3
  

Offensichtlich wird @includeAll = 1 in diesem Fall falsch ausgewertet;   Durch das Hinzufügen wird jedoch die Zeit der Abfrage erhöht, als ob sie wäre   immer wahr.

Dies geschieht, weil diese beiden Prädikate SQL Server zwingen, einen Index|Table Scan -Operator auszuwählen. Warum ?

Der Ausführungsplan wird für alle möglichen Werte von @includeAll Variable / Parameter generiert. Daher wird derselbe Ausführungsplan verwendet, wenn @includeAll = 0 und @includeAll = 1 . Wenn @includeAll = 0 wahr ist und wenn ein Index für die Id -Spalte vorhanden ist, könnte SQL Server ein Index Seek oder Index Seek + Key|RID Lookup verwenden, um die Zeilen zu finden. Aber wenn @includeAll = 1 wahr ist, ist der optimale Datenzugriffsoperator Index|Table Scan . Wenn der Ausführungsplan für alle Werte von @includeAll variable verwendbar sein muss, was ist der Datenzugriffsoperator, der von SQL Server verwendet wird: Suchen oder Scannen? Die Antwort ist unten, wo Sie eine ähnliche Abfrage finden können:

%Vor%

Diese Abfragen haben die folgenden Ausführungspläne:

Wie Sie sehen, enthält der erste Ausführungsplan einen Clustered Index Scan mit zwei not optimized Prädikaten.

Meine Lösung basiert auf dynamischen Abfragen und generiert abhängig vom Wert von @includeAll variable zwei verschiedene Abfragen:

[1] Bei @includeAll = 0 ist die generierte Abfrage ( @SqlStatement )

%Vor%

und der Ausführungsplan enthält ein Index Seek (wie Sie im Bild oben sehen können) und

[2] Bei @includeAll = 1 ist die generierte Abfrage ( @SqlStatement )

%Vor%

und der Ausführungsplan enthält ein Clustered Index Scan . Diese zwei generierten Abfragen haben unterschiedliche optimale Ausführungspläne.

Hinweis: Ich habe Adventure Works 2012 Beispieldatenbank

verwendet     
Bogdan Sahlean 19.05.2014 17:07
quelle
2

Meine Vermutung wäre Parameter-Sniffing - die Prozedur, die kompiliert wurde, wenn @includeAll 1 war, und dies ist der Abfrageplan, der zwischengespeichert wurde. Das heißt, wenn es falsch ist, führen Sie immer noch einen vollständigen Tabellenscan durch, wenn die Suche nach dem Schlüssel und die Suche nach Schlüsseln schneller sind.

Ich denke, der beste Weg, dies zu tun ist:

%Vor%

Oder Sie können jedes Mal, wenn es ausgeführt wird, die Neukompilierung erzwingen:

%Vor%

Um dies weiter zu demonstrieren, habe ich eine sehr einfache Tabelle eingerichtet und sie mit unsinnigen Daten gefüllt:

%Vor%

Ich habe dann die gleiche Abfrage viermal ausgeführt.

  1. Wenn @IncludeAll auf 1 gesetzt ist, verwendet der Abfrageplan einen Tabellenscan und der Plan wird zwischengespeichert
  2. Gleiche Abfrage mit @IncludeAll auf false gesetzt, der Plan mit dem Tabellenscan wird noch zwischengespeichert, so dass er verwendet wird.
  3. Löschen Sie den Cache der Pläne und führen Sie die Abfrage erneut mit @IncludeAll false aus, so dass der Plan jetzt kompiliert und mit einem Index-Such- und Lesezeichen-Lookup gespeichert wird.
  4. Führen Sie mit @IncludeAll auf true festgelegt. Der Index-Suchvorgang und der Suchvorgang werden erneut verwendet.
%Vor%

Die Überprüfung der Ausführungspläne zeigt, dass nach der Kompilierung der Abfrage derselbe Plan ungeachtet des Parameterwerts wiederverwendet wird und dass der Plan für den Wert, der bei der ersten und nicht bei der höchsten Ausführung übergeben wurde, zwischengespeichert wird flexible Basis.

    
GarethD 19.05.2014 16:17
quelle
0

Wenn Sie eine ODER-Anweisung in Ihre SQL-Datei schreiben, führt dies zu einer Überprüfung. Es ist wahrscheinlich Scannen Sie Ihre gesamte Tabelle oder Index, die in einer sehr großen Tabelle äußerst ineffizient sein wird.

Wenn Sie sich den Abfrageplan für eine Abfrage ohne den @ includeAll-Teil ansehen, würden Sie wahrscheinlich eine Indexsuchoperation sehen. Sobald Sie das hinzufügen ODER ändern Sie höchstwahrscheinlich den Abfrageplan in einen Tabellen- / Index-Scan. Sie sollten sich den Plan für Ihre Abfrage ansehen, um zu sehen, was genau sie macht.

    
Dismissile 19.05.2014 16:04
quelle
0

Wenn Sql Server einen Abfrageplan erstellt, muss er einen Plan erstellen, der für alle möglichen Werte eingebetteter Variablen funktioniert. In Ihrem Fall würde eine Indexsuche die besten Ergebnisse liefern, wenn @IncludeAll = 0, aber eine Indexsuche kann nicht im Ereignis @IncludeAll = 1 verwendet werden. Daher hat der Abfrageoptimierer keine andere Wahl als den Abfrageplan zu verwenden, der für beide funktionieren würde Werte von @ IncludeAll. Das führt zu einem Tabellenscan.

    
Aheho 19.05.2014 16:16
quelle
0

Dies liegt daran, dass der SQL-Server keinen Kurzschluss verursacht, wenn Sie OR in where-Klausel haben.

Normalerweise in anderen Programmiersprachen, wenn wir eine Bedingung wie

haben %Vor%

Wenn Param1 = 1 ist, würde es nicht einmal den anderen Ausdruck bewerten.

In Sql Server, wenn Sie OR in Ihrer where-Klausel ähnlich haben wie in Ihrer Abfrage

%Vor%

Auch wenn @includeAll = 1 als wahr bewertet wird, könnte es trotzdem weitergehen und trotzdem nach der zweiten Bedingung suchen.

Und das Ändern der Reihenfolge in Ihrer where-Klausel, welcher Ausdruck zuerst ausgewertet wird, macht keinen Unterschied, da dies der Verlaufsoptimierer zur Laufzeit bestimmt. Sie können dieses Verhalten von SQL Server nicht steuern. Kurzzusammenfassung der Ausdruckauswertung oder Reihenfolge, in der Ausdrücke ausgewertet werden.

    
M.Ali 19.05.2014 16:16
quelle

Tags und Links