Próbuję stworzyć przykładowy plan zapytań, aby pokazać, dlaczego UNIONing dwóch zestawów wyników może być lepszy niż użycie OR w klauzuli JOIN. Napisany przeze mnie plan zapytań mnie zaskoczył. Korzystam z bazy danych StackOverflow z indeksem nieklastrowanym na 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
Plan zapytań znajduje się na stronie https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , czas trwania zapytania dla mnie wynosi 4:37 min, zwrócono 26612 wierszy.
Nie widziałem wcześniej, aby ten styl ciągłego skanowania był tworzony z istniejącej tabeli - nie jestem pewien, dlaczego ciągły skan jest uruchamiany dla każdego wiersza, gdy stały skan jest zwykle używany dla pojedynczego wiersza wprowadzonego przez użytkownika na przykład SELECT GETDATE (). Dlaczego jest tu używany? Byłbym bardzo wdzięczny za wskazówki dotyczące czytania tego planu zapytań.
Jeśli podzielę to OR na UNION, powstanie standardowy plan działający w 12 sekund z tymi samymi zwróconymi 26612 wierszami.
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
Interpretuję ten plan jako następujący:
- Zbierz wszystkie 41782500 wierszy z postów (rzeczywista liczba wierszy odpowiada skanowi CI na postach)
- Dla każdego 41782500 wierszy w postach:
- Produkuj skalary:
- Expr1005: OwnerUserId
- Expr1006: OwnerUserId
- Expr1004: Wartość statyczna 62
- Expr1008: LastEditorUserId
- Expr1009: LastEditorUserId
- Expr1007: Wartość statyczna 62
- W konkatenacie:
- Exp1010: Jeśli Expr1005 (OwnerUserId) nie ma wartości NULL, użyj tego innego, użyj Expr1008 (LastEditorUserID)
- Expr1011: Jeśli Expr1006 (OwnerUserId) nie ma wartości NULL, użyj tego, w przeciwnym razie użyj Expr1009 (LastEditorUserId)
- Expr1012: Jeśli Expr1004 (62) ma wartość NULL, użyj tego, w przeciwnym razie użyj Expr1007 (62)
- W skalarach obliczeniowych: nie wiem, co robi ampersand.
- Expr1013: 4 [i?] 62 (Expr1012) = 4, a OwnerUserId IS NULL (NULL = Expr1010)
- Expr1014: 4 [i?] 62 (Expr1012)
- Expr1015: 16 i 62 (Expr1012)
- W porządku Sortuj według:
- Expr1013 Desc
- Expr1014 Asc
- Expr1010 Asc
- Expr1015 Opis
- W interwale scalania usunięto Expr1013 i Expr1015 (są to dane wejściowe, ale nie wyjściowe)
- W indeksie Szukaj poniżej łączenie zagnieżdżonych pętli używa Expr1010 i Expr1011 jako predykatów szukania, ale nie rozumiem, w jaki sposób ma do nich dostęp, gdy nie wykonał łączenia zagnieżdżonej pętli od IX_NC_REPUTATION do poddrzewa zawierającego Expr1010 i Expr1011 .
- Łączenie w zagnieżdżonych pętlach zwraca tylko identyfikatory użytkowników, które pasują do wcześniejszego poddrzewa. Z powodu przepychania predykatu zwracane są wszystkie wiersze zwrócone z wyszukiwania indeksu IX_NC_REPUTATION.
- Ostatnie dołączone zagnieżdżone pętle: Dla każdego rekordu Postów wyślij Users.Id, gdzie znaleziono dopasowanie w poniższym zestawie danych.
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) ) ;
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) ) ;