Database
 sql >> Datenbank >  >> RDS >> Database

Zeilenziele, Teil 3:Anti-Joins

Dieser Beitrag ist Teil einer Reihe von Artikeln über Zeilenziele. Die anderen Teile findest du hier:

  • Teil 1:Festlegen und Identifizieren von Zeilenzielen
  • Teil 2:Semi-Joins

Dieser Teil behandelt, wann und warum der Optimierer ein Zeilenziel für einen Anti-Join einführt.

Einführung

Ein Anti-Join wird auch als Anti-Semi-Join bezeichnet. Es gibt jede Zeile aus Join-Eingabe A zurück, für die keine Übereinstimmung finden Sie am Eingang B.

Für einen Anti-Join:

  • Der Optimierer kann Fügen Sie einem Anwenden ein innerseitiges Zeilenziel hinzu (Verknüpfung mit korrelierten verschachtelten Schleifen) nur Anti-Verknüpfung .
  • Ein Zeilenziel wird nicht hinzugefügt für nicht korrelierte verschachtelte Schleifen Anti-Join, Hash-Anti-Join oder Merge-Anti-Join.
  • Wie immer wird jedes Zeilenziel nur hinzugefügt wenn es niedriger als die Schätzung ohne angewendetes Zeilenziel ist.
  • Redundante Innenseite TOP -Klauseln und DISTINCT/GROUP BY Operationen können vereinfacht werden.

Ausgehend vom ersten Aufzählungspunkt oben besteht der Hauptunterschied zwischen den Zielen Semi Join anwenden und Anti Join Row anwenden:

  • Ein Semi Join anwenden enthält immer ein Zeilenziel (solange es weniger als die Schätzung ohne das Ziel ist).
  • Ein Apply Anti Join kann ein Zeilenziel enthalten , aber nur, wenn ein logischer Anti-Join während der kostenbasierten Optimierung in einen Apply umgewandelt wird .

Ich entschuldige mich dafür, dass diese Regeln nicht einfacher sind, aber ich habe sie nicht gemacht. Hoffentlich werden einige Diskussionen und Beispiele alles klarer machen.

Kein Anti-Join-Row-Ziel standardmäßig

Der Optimierer geht davon aus, dass Personen einen Semi-Join schreiben (indirekt z.B. über EXISTS ) mit der Erwartung, dass die gesuchte Zeile gefunden wird . Ein Zeilenziel anwenden ist festgelegt vom Optimierer, um die erwartete übereinstimmende Zeile schnell zu finden.

Für Anti-Beitritt (ausgedrückt z.B. mit NOT EXISTS ) geht der Optimierer davon aus, dass eine übereinstimmende Zeile nicht gefunden wird . Ein Zeilenziel zum Anwenden von Anti-Beitritt ist nicht festgelegt durch den Optimierer, da er erwartet, alle Zeilen überprüfen zu müssen, um zu bestätigen, dass es keine Übereinstimmung gibt.

Wenn sich herausstellt, dass es eine übereinstimmende Zeile gibt, kann es länger dauern, bis Anti-Join anwenden diese Zeile findet, als wenn ein Zeilenziel verwendet worden wäre. Trotzdem beendet der Anti-Join seine Suche, sobald die (unerwartete) Übereinstimmung gefunden wird.

Wenden Sie Anti-Join-Row-Zielbedingungen an

SQL Server bietet uns keine Möglichkeit, direkt einen Anti-Join zu schreiben, daher müssen wir Problemumgehungen wie NOT EXISTS verwenden , NOT IN/ANY/SOME , oder EXCEPT . Jede dieser Formen führt zu Beginn der Abfragekompilierung zu einer Unterabfragedarstellung im analysierten Baum. Diese Unterabfrage wird immer in eine Anwendung entrollt und dann wo möglich in einen logischen Anti-Join umgewandelt (Die Details sind dieselben wie für Semi Join, die in Teil 2 besprochen wurden). Dies alles passiert, bevor auch nur ein trivialer Plan in Betracht gezogen wird.

Damit ein Anti-Join ein Zeilenziel erhält, muss es eintreten kostenbasierte Optimierung als logischer Anti-Join (was bedeutet, dass die Transformation von einem Apply oben erfolgreich gewesen sein muss). Dann muss der kostenbasierte Optimierer entscheiden, den logischen Anti-Join als Anwenden zu implementieren . Dazu muss sich der Optimierer zunächst für Erkunden entscheiden die Anwenden-Option; dann muss es auswählen das als die billigste Option (für diesen Teil des Plans).

Ein Anti-Join-Zeilenziel wird durch eine der kostenbasierten Optimierungsregeln festgelegt, die einen Join in einen Apply umwandeln können. Ein Anti-Join, der eintritt kostenbasierte Optimierung als Anwendung (weil die Umwandlung in einen logischen Anti-Join fehlgeschlagen ist) nicht haben ein Zeilenziel angewendet.

Der kostenbasierte Optimierer untersucht und wählt die Join-to-Apply-Option nur aus, wenn es eine effiziente Möglichkeit gibt, passende innere Seitenreihen zu finden (z. B. unter Verwendung eines Index). Der Optimierer untersucht die Option nicht, wenn dem Teilbaum der inneren Seite des Joins irgendetwas fehlt, das nützlich ist, damit sich das Apply-Prädikat "einklinken" kann. Dies kann ein Index, ein temporärer Index (über einen Eifer-Index-Spool) oder ein anderer logischer Schlüssel sein. Das Hinzufügen des Zeilenziels hilft dem Optimierer, die Kosten der Join-to-Apply-Option abzuschätzen, da maximal eine Zeile lokalisiert werden muss.

Beachten Sie, dass ein Anti-Join anwenden in einem Ausführungsplan ohne Zeilenziel angezeigt werden kann. Dies tritt auf, wenn die anfängliche Transformation von apply zu join fehlschlägt, was relativ häufig vorkommt. Wenn dies geschieht, startet der Anti-Join im kostenbasierten Optimierer als Anwenden und hat daher nie ein Zeilenziel, das durch eine der Join-to-Apply-Regeln hinzugefügt wird.

Natürlich kann ein Zeilenziel auch auf der Innenseite dieser Anwendung über einen anderen Mechanismus (der nicht mit der Anwendung verbunden ist) eingeführt werden, beispielsweise durch einen separaten Top-Operator.

Zusammenfassend:

  • Ein Anti-Join kann nur während der kostenbasierten Optimierung (CBO) ein Zeilenziel erreichen.
  • Regeln, die einen Anti-Join in ein Anwenden umwandeln, fügen ein Zeilenziel hinzu.
  • Der Anti-Join muss CBO als Join eingeben, nicht als Apply.
  • Um CBO als Join einzugeben, müssen frühere Phasen in der Lage sein, die Unterabfrage als Join umzuschreiben (über eine Anwendungsphase).
  • CBO erkundet den Join, um die Transformation nur in vielversprechenden Fällen anzuwenden.

Beispiel

Es ist etwas schwieriger, all dies für apply anti join zu demonstrieren, als dies für apply semi join der Fall war. Die Gründe dafür werden in Teil 4 behandelt.

In der Zwischenzeit ist hier ein AdventureWorks-Beispiel, das zeigt, wie ein Apply-Anti-Join mit Zeilenziel entsteht, wobei dieselben undokumentierten Ablaufverfolgungsflags wie für Semi-Join verwendet werden. Das Trace-Flag 8608 wird hinzugefügt, um die anfängliche Memostruktur beim Start der kostenbasierten Optimierung anzuzeigen.

SELECT P.ProductID 
FROM Production.Product AS P
WHERE 
    NOT EXISTS 
    (
        SELECT 1
        FROM Production.TransactionHistoryArchive AS THA 
        WHERE THA.ProductID = P.ProductID
 
        UNION ALL
 
        SELECT 1
        FROM Production.TransactionHistory AS TH 
        WHERE TH.ProductID = P.ProductID
    )
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8608, QUERYTRACEON 8612, QUERYTRACEON 8621);

Die Unterabfrage "exists" wird zuerst in eine apply:

umgewandelt