J'essaie de produire un exemple de plan de requête pour montrer pourquoi UNIONner deux ensembles de résultats peut être meilleur que d'utiliser OR dans une clause JOIN. Un plan de requête que j'ai écrit m'a laissé perplexe. J'utilise la base de données StackOverflow avec un index non cluster sur 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
Le plan de requête est à https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , la durée de la requête pour moi est de 4:37 min, 26612 lignes retournées.
Je n'ai jamais vu ce style de scan constant créé à partir d'une table existante auparavant - je ne sais pas pourquoi il y a un scan constant exécuté pour chaque ligne, alors qu'un scan constant est généralement utilisé pour une seule ligne entrée par l'utilisateur par exemple SELECT GETDATE (). Pourquoi est-il utilisé ici? J'apprécierais vraiment quelques conseils pour lire ce plan de requête.
Si je divise ce OU en UNION, cela produit un plan standard exécuté en 12 secondes avec les mêmes 26612 lignes renvoyées.
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
J'interprète ce plan comme ceci:
- Obtenez toutes les 41782500 lignes des publications (le nombre réel de lignes correspond à l'analyse CI des publications)
- Pour chaque 41782500 lignes dans les messages:
- Produire des scalaires:
- Expr1005: OwnerUserId
- Expr1006: OwnerUserId
- Expr1004: La valeur statique 62
- Expr1008: LastEditorUserId
- Expr1009: LastEditorUserId
- Expr1007: La valeur statique 62
- Dans le concaténé:
- Exp1010: Si Expr1005 (OwnerUserId) n'est pas nul, utilisez-le sinon utilisez Expr1008 (LastEditorUserID)
- Expr1011: Si Expr1006 (OwnerUserId) n'est pas nul, utilisez-le, sinon utilisez Expr1009 (LastEditorUserId)
- Expr1012: Si Expr1004 (62) est nul, utilisez cela, sinon utilisez Expr1007 (62)
- Dans le scalaire de calcul: je ne sais pas ce que fait une esperluette.
- Expr1013: 4 [et?] 62 (Expr1012) = 4 et OwnerUserId IS NULL (NULL = Expr1010)
- Expr1014: 4 [et?] 62 (Expr1012)
- Expr1015: 16 et 62 (Expr1012)
- Dans l'ordre Trier par:
- Expr1013 Desc
- Expr1014 Asc
- Expr1010 Asc
- Expr1015 Desc
- Dans l'intervalle de fusion, il a supprimé Expr1013 et Expr1015 (ce sont des entrées mais pas des sorties)
- Dans la recherche d'index sous la jointure de boucles imbriquées, il utilise Expr1010 et Expr1011 comme prédicats de recherche, mais je ne comprends pas comment il y accède lorsqu'il n'a pas effectué la jointure de boucle imbriquée de IX_NC_REPUTATION au sous-arbre contenant Expr1010 et Expr1011 .
- La jointure des boucles imbriquées renvoie uniquement les utilisateurs.ID qui ont une correspondance dans la sous-arborescence précédente. En raison du pushdown de prédicat, toutes les lignes renvoyées par la recherche d'index sur IX_NC_REPUTATION sont retournées.
- La dernière jointure de boucles imbriquées: pour chaque enregistrement de publications, affichez Users.Id où une correspondance se trouve dans l'ensemble de données ci-dessous.
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) ) ;