Merkwürdiger Abfrageplan bei Verwendung von OR in der JOIN-Klausel - Konstanter Scan für jede Zeile in der Tabelle


10

Ich versuche einen Beispiel-Abfrageplan zu erstellen, um zu zeigen, warum die UNIONierung von zwei Ergebnismengen besser ist als die Verwendung von OR in einer JOIN-Klausel. Ein Abfrageplan, den ich geschrieben habe, hat mich ratlos gemacht. Ich verwende die StackOverflow-Datenbank mit einem nicht gruppierten Index für Users.Reputation.

Abfrageplan-Image Die Abfrage lautet

CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts  
    ON Users.Id = Posts.OwnerUserId
    OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

Der Abfrageplan ist unter https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE abrufbar. Die Abfragedauer beträgt für mich 4:37 Minuten, es werden 26612 Zeilen zurückgegeben.

Ich habe noch nie gesehen, dass ein solcher Konstantenscan aus einer vorhandenen Tabelle erstellt wurde. Ich bin nicht damit vertraut, warum ein Konstantenscan für jede einzelne Zeile ausgeführt wird, wenn normalerweise ein Konstantenscan für eine einzelne vom Benutzer eingegebene Zeile verwendet wird Zum Beispiel SELECT GETDATE (). Warum wird es hier verwendet? Ich würde mich sehr über eine Anleitung zum Lesen dieses Abfrageplans freuen.

Wenn ich dieses ODER in eine UNION aufspalte, wird ein Standardplan erstellt, der in 12 Sekunden ausgeführt wird und dieselben 26612 Zeilen zurückgibt.

SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION 
SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON  Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

Ich interpretiere diesen Plan so:

  • Alle 41782500 Zeilen aus Posts abrufen (die tatsächliche Anzahl der Zeilen entspricht dem CI-Scan für Posts)
  • Für jede 41782500 Zeilen in Posts:
    • Skalare erzeugen:
    • Expr1005: OwnerUserId
    • Expr1006: OwnerUserId
    • Expr1004: Der statische Wert 62
    • Expr1008: LastEditorUserId
    • Expr1009: LastEditorUserId
    • Expr1007: Der statische Wert 62
  • In der Verkettung:
    • Exp1010: Wenn Expr1005 (OwnerUserId) nicht null ist, verwenden Sie diese Option, andernfalls Expr1008 (LastEditorUserID).
    • Expr1011: Wenn Expr1006 (OwnerUserId) nicht null ist, verwenden Sie dies, andernfalls verwenden Sie Expr1009 (LastEditorUserId).
    • Expr1012: Wenn Expr1004 (62) null ist, verwende das, sonst verwende Expr1007 (62)
  • Im Rechenskalar: Ich weiß nicht, was ein kaufmännisches Und tut.
    • Beispiel1013: 4 [und?] 62 (Beispiel1012) = 4 und OwnerUserId IS NULL (NULL = Beispiel1010)
    • Beispiel1014: 4 [und?] 62 (Beispiel1012)
    • Beispiel1015: 16 und 62 (Beispiel1012)
  • In der Reihenfolge nach sortieren nach:
    • Expr1013 beschr
    • Expr1014 Asc
    • Expr1010 Asc
    • Expr1015 beschr
  • Im Zusammenführungsintervall wurden Expr1013 und Expr1015 entfernt (dies sind Eingaben, aber keine Ausgaben).
  • In der Indexsuche unterhalb des Joins für verschachtelte Schleifen werden Expr1010 und Expr1011 als Suchprädikate verwendet, aber ich verstehe nicht, wie auf diese zugegriffen werden kann, wenn der Join für verschachtelte Schleifen von IX_NC_REPUTATION auf den Teilbaum mit Expr1010 und Expr1011 nicht ausgeführt wurde .
  • Der Join mit verschachtelten Schleifen gibt nur die Users.IDs zurück, die mit dem vorherigen Teilbaum übereinstimmen. Aufgrund von Prädikat-Pushdown werden alle Zeilen zurückgegeben, die von der Indexsuche für IX_NC_REPUTATION zurückgegeben wurden.
  • Der letzte Join mit verschachtelten Schleifen: Geben Sie für jeden Posts-Datensatz Users.Id aus, wobei im folgenden Datensatz eine Übereinstimmung gefunden wird.

Haben Sie es mit einer EXISTS-Unterabfrage oder Unterabfragen versucht? SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND ( EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

Eine Unterabfrage:SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id IN (Posts.OwnerUserId, Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

Antworten:


10

Der Plan ähnelt dem, auf den ich hier näher eingehen werde .

Der PostsTisch wird gescannt.

Für jede Zeile werden die OwnerUserIdund extrahiert LastEditorUserId. Dies ist in ähnlicher Weise wie die Arbeitsweise UNPIVOT. Im folgenden Plan sehen Sie einen einzelnen Konstantenscanoperator, der die zwei Ausgabezeilen für jede Eingabezeile erstellt.

SELECT *
FROM dbo.Posts
UNPIVOT (X FOR U IN (OwnerUserId,LastEditorUserId)) Unpvt

In diesem Fall ist der Plan etwas komplexer, da die Semantik dafür lautet, ordass bei gleichen Spaltenwerten nur eine Zeile aus dem Join ausgegeben werden soll Users(nicht zwei).

Diese werden dann durch das Zusammenführungsintervall geführt, so dass bei gleichen Werten der Bereich reduziert wird und nur ein Suchvorgang ausgeführt wird Users- andernfalls werden zwei Suchvorgänge ausgeführt.

Der Wert 62ist ein Flag, das bedeutet, dass die Suche eine Gleichheitssuche sein sollte.

Bezüglich

Ich verstehe nicht, wie es auf diese zugreifen kann, wenn es den Nested-Loop-Join von IX_NC_REPUTATION zum Teilbaum mit Expr1010 und Expr1011 nicht ausgeführt hat

Diese sind im gelb hervorgehobenen Verkettungsoperator definiert. Dies befindet sich auf der Außenseite der gelb hervorgehobenen verschachtelten Schleifen. Dies wird ausgeführt, bevor die gelb hervorgehobene Suche im Inneren dieser verschachtelten Schleifen erfolgt.

Bildbeschreibung hier eingeben

Eine Umschreibung, die einen ähnlichen Plan enthält (wobei das Zusammenführungsintervall durch eine Zusammenführungsvereinigung ersetzt wird), ist unten aufgeführt, falls dies hilfreich ist.

SELECT DISTINCT D2.UserId
FROM   dbo.Posts p
       CROSS APPLY (SELECT Users.Id AS UserId
                    FROM   (SELECT p.OwnerUserId
                            UNION /*collapse duplicate to single row*/
                            SELECT p.LastEditorUserId) D1(UserId)
                           JOIN Users
                             ON Users.Id = D1.UserId) D2
OPTION (FORCE ORDER) 

Bildbeschreibung hier eingeben

Abhängig davon, welche Indizes in der PostsTabelle verfügbar sind, ist eine Variante dieser Abfrage möglicherweise effizienter als die von Ihnen vorgeschlagene UNION ALLLösung. (Die Kopie der Datenbank, über die ich verfüge, hat hierfür keinen nützlichen Index, und die vorgeschlagene Lösung führt zwei vollständige Scans durch Posts. Das Folgende führt sie in einem Scan durch.)

WITH Unpivoted AS
(
SELECT UserId
FROM dbo.Posts
UNPIVOT (UserId FOR U IN (OwnerUserId,LastEditorUserId)) Unpivoted
)
SELECT DISTINCT Users.Id
FROM dbo.Users INNER HASH JOIN Unpivoted
       ON  Users.Id = Unpivoted.UserId
WHERE Users.Reputation = 5

Bildbeschreibung hier eingeben

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.