SQL Server unterteilt A <> B in A <B ODER A> B und führt zu seltsamen Ergebnissen, wenn B nicht deterministisch ist


26

Bei SQL Server ist ein interessantes Problem aufgetreten. Betrachten Sie das folgende Reprobeispiel:

CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');

SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
  AND s_guid <> NEWID();

DROP TABLE #test;

Geige

Bitte vergessen Sie für einen Moment, dass die s_guid <> NEWID()Bedingung völlig unbrauchbar zu sein scheint - dies ist nur ein minimales Repro-Beispiel. Da die Wahrscheinlichkeit, NEWID()mit einem bestimmten konstanten Wert übereinzustimmen, äußerst gering ist, sollte der Wert jedes Mal WAHR sein.

Aber das tut es nicht. Das Ausführen dieser Abfrage gibt normalerweise 1 Zeile zurück, aber manchmal (ziemlich häufig, mehr als 1 Mal von 10) werden 0 Zeilen zurückgegeben. Ich habe es mit SQL Server 2008 auf meinem System reproduziert, und Sie können es online mit der oben verlinkten Geige reproduzieren (SQL Server 2014).

Ein Blick auf den Ausführungsplan zeigt, dass der Abfrageanalysator die Bedingung anscheinend aufteilt in s_guid < NEWID() OR s_guid > NEWID():

Screenshot des Abfrageplans

... was vollständig erklärt, warum es manchmal fehlschlägt (wenn die erste generierte ID kleiner und die zweite größer als die angegebene ID ist).

Ist SQL Server zu bewerten erlaubt A <> Bwie A < B OR A > B, auch wenn einer der Ausdrücke ist nicht deterministisch ? Wenn ja, wo ist es dokumentiert? Oder haben wir einen Bug gefunden?

Interessanterweise AND NOT (s_guid = NEWID())ergibt sich der gleiche Ausführungsplan (und das gleiche zufällige Ergebnis).

Wir haben dieses Problem gefunden, als ein Entwickler eine bestimmte Zeile optional ausschließen und Folgendes verwenden wollte:

s_guid <> ISNULL(@someParameter, NEWID())

als "Abkürzung" für:

(@someParameter IS NULL OR s_guid <> @someParameter)

Ich suche nach Dokumentation und / oder Bestätigung eines Fehlers. Der Code ist nicht allzu relevant, sodass keine Problemumgehungen erforderlich sind.


Antworten:


22

Ist SQL Server zu bewerten erlaubt A <> Bwie A < B OR A > B, auch wenn einer der Ausdrücke ist nicht deterministisch ?

Dies ist ein etwas kontroverser Punkt, und die Antwort ist ein qualifiziertes "Ja".

Die beste Diskussion, die mir bekannt ist, wurde als Antwort auf Itzik Ben-Gans Connect-Fehlerbericht Bug with NEWID and Table Expressions gegeben , der geschlossen wurde, da er nicht behoben werden konnte. Connect wurde inzwischen eingestellt, sodass der Link zu einem Webarchiv besteht. Leider ging durch den Niedergang von Connect viel nützliches Material verloren (oder war schwerer zu finden). Wie auch immer, die nützlichsten Zitate von Jim Hogg von Microsoft sind:

Dies trifft den Kern des Problems: Darf die Optimierung die Semantik eines Programms ändern? Dh: Wenn ein Programm bestimmte Antworten liefert, aber langsam ausgeführt wird, ist es dann legitim, dass ein Abfrageoptimierer dieses Programm schneller ausführt und gleichzeitig die angegebenen Ergebnisse ändert?

Vor dem Schreien "NEIN!" (Auch meine persönliche Neigung :-), bedenke: Die gute Nachricht ist, dass in 99% der Fälle die Antworten die gleichen sind. Die Abfrageoptimierung ist also ein klarer Gewinn. Die schlechte Nachricht ist, dass, wenn die Abfrage nebenwirkenden Code enthält, unterschiedliche Pläne tatsächlich unterschiedliche Ergebnisse liefern können. Und NEWID () ist eine solche nebenwirkende (nicht deterministische) "Funktion", die den Unterschied aufdeckt. [Wenn Sie experimentieren, können Sie sich andere ausdenken - zum Beispiel die Kurzschlussbewertung von AND-Klauseln: Lassen Sie die zweite Klausel eine arithmetische Division durch Null auslösen. Verschiedene Optimierungen können diese zweite Klausel VOR der ersten Klausel ausführen.] Dies spiegelt sich wider Craigs Erklärung an anderer Stelle in diesem Thread, dass SqlServer nicht garantiert, wenn skalare Operatoren ausgeführt werden.

Wir haben also die Wahl: Wenn wir ein bestimmtes Verhalten bei Vorhandensein von nicht deterministischem (nebenwirkendem) Code garantieren möchten, sodass die Ergebnisse von JOINs beispielsweise der Semantik einer Ausführung mit verschachtelten Schleifen folgen, dann wir kann geeignete OPTIONEN verwenden, um dieses Verhalten zu erzwingen - wie UC betont. Der resultierende Code wird jedoch langsam ausgeführt - das sind die Kosten, die tatsächlich für die Beeinträchtigung des Abfrageoptimierungsprogramms anfallen.

Alles in allem verschieben wir das Abfrageoptimierungsprogramm in Richtung "erwartungsgemäßes" Verhalten für NEWID () - und tauschen die Leistung gegen "erwartungsgemäßes Ergebnis" aus.

Ein Beispiel für die Änderung des Verhaltens in dieser Hinsicht im Laufe der Zeit ist, dass NULLIF mit nicht deterministischen Funktionen wie RAND () nicht korrekt funktioniert . Es gibt auch andere ähnliche Fälle, in denen z. B. COALESCEeine Unterabfrage verwendet wird, die zu unerwarteten Ergebnissen führen kann und die ebenfalls schrittweise behandelt werden.

Jim fährt fort:

Den Kreis schließen . . . Ich habe diese Frage mit dem Entwicklerteam besprochen. Und schließlich haben wir aus folgenden Gründen beschlossen, das aktuelle Verhalten nicht zu ändern:

1) Der Optimierer garantiert nicht das Timing oder die Anzahl der Ausführungen von Skalarfunktionen. Dies ist ein langjähriger Grundsatz. Dies ist der grundlegende Spielraum, der dem Optimierer genügend Spielraum lässt, um signifikante Verbesserungen bei der Ausführung von Abfrageplänen zu erzielen.

2) Dieses "einmal pro Zeile" -Verhalten ist kein neues Problem, obwohl es nicht allgemein diskutiert wird. Wir haben angefangen, sein Verhalten in der Yukon-Version zu optimieren. Aber es ist ziemlich schwer, genau zu sagen, was es bedeutet! Gilt dies beispielsweise für Zwischenzeilen, die "auf dem Weg" zum Endergebnis berechnet wurden? - In diesem Fall hängt es eindeutig vom gewählten Plan ab. Oder gilt es nur für die Zeilen, die eventuell im fertigen Ergebnis erscheinen werden? - Hier findet eine üble Rekursion statt, da sind Sie sich sicher einig!

3) Wie bereits erwähnt, verwenden wir standardmäßig die Option "Leistung optimieren" - dies ist in 99% der Fälle der Fall. Die 1% der Fälle, in denen sich das Ergebnis ändern könnte, sind relativ leicht zu erkennen - nebenwirkende "Funktionen" wie NEWID - und leicht zu beheben (als Konsequenz wird Perf. Gehandelt). Diese Standardeinstellung zum erneuten Optimieren der Leistung ist seit langem bekannt und wird akzeptiert. (Ja, es ist nicht die Haltung, die Compiler für herkömmliche Programmiersprachen gewählt haben, aber so soll es sein).

Unsere Empfehlungen lauten also:

a) Vermeiden Sie es, sich auf nicht garantierte Timing- und Ausführungszahlsemantiken zu verlassen. b) Vermeiden Sie NEWID () tief in Tabellenausdrücken. c) Verwenden Sie OPTION, um ein bestimmtes Verhalten zu erzwingen (Trading Perf)

Hoffe, diese Erklärung hilft, unsere Gründe für das Schließen dieses Fehlers zu klären, da "nicht behoben".


Interessanterweise AND NOT (s_guid = NEWID())ergibt sich der gleiche Ausführungsplan

Dies ist eine Folge der Normalisierung, die sehr früh während der Abfragekompilierung auftritt. Beide Ausdrücke werden in genau dieselbe normalisierte Form kompiliert, sodass derselbe Ausführungsplan erstellt wird.


In diesem Fall können wir WITH (FORCESCAN) verwenden, wenn wir einen bestimmten Plan erzwingen möchten, der das Problem zu vermeiden scheint. Um sicherzugehen, sollten wir eine Variable verwenden, um das Ergebnis von NEWID () zu speichern, bevor wir die Abfrage ausführen.
Razvan Socol

11

Dies wird hier dokumentiert:

Die Häufigkeit, mit der eine in einer Abfrage angegebene Funktion tatsächlich ausgeführt wird, kann zwischen den vom Optimierer erstellten Ausführungsplänen variieren. Ein Beispiel ist eine Funktion, die von einer Unterabfrage in einer WHERE-Klausel aufgerufen wird. Die Häufigkeit, mit der die Unterabfrage und ihre Funktion ausgeführt werden, kann je nach den vom Optimierer ausgewählten Zugriffspfaden variieren.

Benutzerdefinierte Funktionen

Dies ist nicht das einzige Abfrageformular, in dem der Abfrageplan NEWID () mehrmals ausführt und das Ergebnis ändert. Dies ist verwirrend, aber tatsächlich wichtig, damit NEWID () für die Schlüsselerzeugung und die zufällige Sortierung nützlich ist.

Am verwirrendsten ist, dass sich nicht alle nicht deterministischen Funktionen tatsächlich so verhalten. Beispielsweise werden RAND () und GETDATE () nur einmal pro Abfrage ausgeführt.


Gibt es einen Blogeintrag oder Ähnliches, der erklärt, warum / wann die Engine "nicht gleich" in einen Bereich konvertiert?
Mister Magoo

3
Nicht, dass ich davon Wüste. Kann Routine sein, weil =, <und >kann effizient gegen einen BTree ausgewertet werden.
David Browne - Microsoft

5

Wenn Sie sich dieses alte SQL 92-Standarddokument ansehen , werden die Anforderungen bezüglich der Ungleichung im Abschnitt " 8.2 <comparison predicate>" wie folgt beschrieben:

1) Es seien X und Y jeweils zwei entsprechende <Zeilenwertkonstruktorelemente>. Sei XV und YV die durch X bzw. Y dargestellten Werte.

[...]

ii) "X <> Y" ist genau dann wahr, wenn XV und YV nicht gleich sind.

[...]

7) Sei Rx und Ry die beiden <Zeilenwertkonstruktoren> des <Vergleichsprädikats> und sei RXi und RYi das i-te <Zeilenwertkonstruktorelement> von Rx bzw. Ry. "Rx <comp op> Ry" ist wie folgt wahr, falsch oder unbekannt:

[...]

b) "x <> Ry" ist genau dann wahr, wenn RXi <> RYi für einige i gilt.

[...]

h) "x <> Ry" ist genau dann falsch, wenn "Rx = Ry" wahr ist.

Hinweis: Ich habe der Vollständigkeit halber 7b und 7h eingefügt, da es sich um <>Vergleiche handelt. Ich glaube nicht, dass der Vergleich von Zeilenwertkonstruktoren mit mehreren Werten in T-SQL implementiert ist, es sei denn, ich verstehe massiv falsch, was dies sagt - was durchaus möglich ist

Das ist ein Haufen verwirrenden Mülls. Aber wenn Sie weiter auf dem Müll tauchen wollen ...

Ich denke, dass 1.ii das Element ist, das in diesem Szenario angewendet wird, da wir die Werte von "Zeilenwertkonstruktorelementen" vergleichen.

ii) "X <> Y" ist genau dann wahr, wenn XV und YV nicht gleich sind.

Grundsätzlich X <> Ygilt, wenn die durch X und Y dargestellten Werte nicht gleich sind. Da X < Y OR X > Yes sich um eine logisch äquivalente Umschreibung dieses Prädikats handelt, ist es für den Optimierer absolut cool, diese zu verwenden.

Der Standard legt für diese Definition keine Einschränkungen in Bezug auf die Deterministik (oder was auch immer Sie daraus ableiten) der Zeilenwertkonstruktorelemente auf beiden Seiten des <>Vergleichsoperators fest. Es liegt in der Verantwortung des Benutzercodes, mit der Tatsache umzugehen, dass ein Werteausdruck auf einer Seite möglicherweise nicht deterministisch ist.


1
Ich werde nicht abstimmen (hoch oder runter), aber ich bin nicht überzeugt. In den von Ihnen angegebenen Anführungszeichen wird "Wert" angegeben . Ich verstehe, dass es sich um einen Vergleich zwischen zwei Werten auf jeder Seite handelt. Nicht zwischen zwei (oder mehr) Instanzen eines Wertes auf jeder Seite. Außerdem erwähnt der Standard (mindestens der 92, den Sie zitieren) überhaupt nicht deterministische Funktionen nicht. Aus einer ähnlichen Überlegung wie Ihrer können wir annehmen, dass ein SQL-Produkt, das dem Standard entspricht, keine nicht deterministische Funktion bietet, sondern nur die im Standard genannten.
ypercubeᵀᴹ

@yper danke für das feedback! Ich denke, Ihre Interpretation ist definitiv gültig. Dies ist das erste Mal, dass ich dieses Dokument gelesen habe. Es werden Werte im Kontext des Werts erwähnt, der von einem "Zeilenwertkonstruktor" dargestellt wird, der an anderer Stelle im Dokument eine skalare Unterabfrage sein kann (unter anderem). Insbesondere die skalare Unterabfrage scheint nicht deterministisch zu sein. Aber ich weiß wirklich nicht, wovon ich spreche =)
Josh Darnell
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.