Sto cercando di produrre un piano di query di esempio per mostrare perché UNIONing due set di risultati può essere migliore dell'utilizzo di OR in una clausola JOIN. Un piano di query che ho scritto mi ha lasciato perplesso. Sto usando il database StackOverflow con un indice non cluster su 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
Il piano di query è all'indirizzo https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , la durata della query per me è di 4:37 min, 26612 righe restituite.
Non ho mai visto questo stile di scansione costante creato da una tabella esistente in precedenza: non ho familiarità con il motivo per cui viene eseguita una scansione costante per ogni singola riga, quando di solito viene utilizzata una scansione costante per una singola riga immessa dall'utente ad esempio SELEZIONA GETDATE (). Perché è usato qui? Gradirei davvero alcune indicazioni nella lettura di questo piano di query.
Se lo suddivido in un UNION, produce un piano standard in esecuzione in 12 secondi con le stesse 26612 righe restituite.
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
Interpreto questo piano nel modo seguente:
- Ricevi tutte le 41782500 righe dai post (il numero effettivo di righe corrisponde alla scansione CI sui post)
- Per ogni 41782500 righe nei post:
- Produrre scalari:
- Expr1005: OwnerUserId
- Expr1006: OwnerUserId
- Expr1004: il valore statico 62
- Expr1008: LastEditorUserId
- Expr1009: LastEditorUserId
- Expr1007: il valore statico 62
- Nel concatenato:
- Exp1010: Se Expr1005 (OwnerUserId) non è null, utilizzare quell'altro utilizzare Expr1008 (LastEditorUserID)
- Expr1011: Se Expr1006 (OwnerUserId) non è null, usalo, altrimenti usa Expr1009 (LastEditorUserId)
- Expr1012: Se Expr1004 (62) è null, usalo, altrimenti usa Expr1007 (62)
- Nello scalare di calcolo: non so cosa faccia una e commerciale.
- Expr1013: 4 [e?] 62 (Expr1012) = 4 e OwnerUserId IS NULL (NULL = Expr1010)
- Expr1014: 4 [e?] 62 (Expr1012)
- Expr1015: 16 e 62 (Expr1012)
- Nell'ordine per ordina per:
- Expr1013 Desc
- Espr.1014 Asc
- Expr1010 Asc
- Expr1015 Desc
- Nell'intervallo di merge ha rimosso Expr1013 ed Expr1015 (questi sono input ma non output)
- Nell'indice cerca sotto i loop nidificati join sta usando Expr1010 ed Expr1011 come predicati di ricerca, ma non capisco come abbia accesso a questi quando non ha eseguito il join loop nidificato da IX_NC_REPUTATION alla sottostruttura contenente Expr1010 ed Expr1011 .
- Il join Nested Loops restituisce solo gli Users.ID che hanno una corrispondenza nella sottostruttura precedente. A causa del pushdown del predicato, vengono restituite tutte le righe restituite dall'indice seek su IX_NC_REPUTATION.
- Gli ultimi loop nidificati si uniscono: per ogni record di messaggi, genera Users.Id in cui viene trovata una corrispondenza nel set di dati di seguito.
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) ) ;