我正在尝试生成一个示例查询计划,以说明为什么对两个结果集进行UNIONing可能比在JOIN子句中使用OR更好。我写的查询计划让我感到困惑。我将StackOverflow数据库与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
查询计划位于https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE,对我来说查询时间为4:37分钟,返回了26612行。
我以前从未见过从现有表中创建过这种恒定扫描的样式-我不熟悉为什么在用户输入的单行通常使用恒定扫描的情况下,每行都要进行恒定扫描的原因例如SELECT GETDATE()。为什么在这里使用它?在阅读此查询计划时,我将非常感谢一些指导。
如果我将该OR拆分为一个UNION,它将生成一个标准计划,该计划在12秒内运行,并返回相同的26612行。
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
我将此计划解释为:
- 从帖子中获取所有41782500行(实际行数与帖子上的CI扫描匹配)
- 对于帖子中的每41782500行:
- 产生标量:
- Expr1005:OwnerUserId
- Expr1006:OwnerUserId
- Expr1004:静态值62
- Expr1008:LastEditorUserId
- Expr1009:LastEditorUserId
- Expr1007:静态值62
- 在串联中:
- Exp1010:如果Expr1005(OwnerUserId)不为null,则使用该属性,否则使用Expr1008(LastEditorUserID)
- Expr1011:如果Expr1006(OwnerUserId)不为null,请使用它,否则使用Expr1009(LastEditorUserId)
- Expr1012:如果Expr1004(62)为空,则使用它,否则使用Expr1007(62)
- 在计算标量中:我不知道“&”号的作用。
- Expr1013:4 [and?] 62(Expr1012)= 4,并且OwnerUserId为NULL(NULL = Expr1010)
- Expr1014:4 [和?] 62(Expr1012)
- Expr1015:16和62(Expr1012)
- 在排序依据中:
- Expr1013描述
- Expr1014 Asc
- Expr1010 Asc
- Expr1015描述
- 在合并间隔中,它删除了Expr1013和Expr1015(这些是输入但不是输出)
- 在嵌套循环连接下面的索引查找中,它使用Expr1010和Expr1011作为查找谓词,但是当它没有完成从IX_NC_REPUTATION到包含Expr1010和Expr1011的子树的嵌套循环连接时,我不明白它如何访问这些索引。 。
- 嵌套循环联接仅返回在较早的子树中具有匹配项的Users.ID。由于谓词下推,将返回从IX_NC_REPUTATION上的索引搜索返回的所有行。
- 最后一个嵌套循环连接:对于每个Posts记录,输出在以下数据集中找到匹配项的Users.Id。
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) ) ;