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.
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.
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) ) ;
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) ) ;
Antworten:
Der Plan ähnelt dem, auf den ich hier näher eingehen werde .
Der
Posts
Tisch wird gescannt.Für jede Zeile werden die
OwnerUserId
und extrahiertLastEditorUserId
. Dies ist in ähnlicher Weise wie die ArbeitsweiseUNPIVOT
. Im folgenden Plan sehen Sie einen einzelnen Konstantenscanoperator, der die zwei Ausgabezeilen für jede Eingabezeile erstellt.In diesem Fall ist der Plan etwas komplexer, da die Semantik dafür lautet,
or
dass bei gleichen Spaltenwerten nur eine Zeile aus dem Join ausgegeben werden sollUsers
(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
62
ist ein Flag, das bedeutet, dass die Suche eine Gleichheitssuche sein sollte.Bezüglich
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.
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.
Abhängig davon, welche Indizes in der
Posts
Tabelle verfügbar sind, ist eine Variante dieser Abfrage möglicherweise effizienter als die von Ihnen vorgeschlageneUNION ALL
Lö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 durchPosts
. Das Folgende führt sie in einem Scan durch.)quelle