SEa*_*986 4 sql-server optimization execution-plan sql-server-2019 query-performance
我在 StackOverflow 数据库中有以下 [相当无意义,仅用于演示] 查询:
SELECT *
FROM Users u
LEFT JOIN Comments c
ON u.Id = c.UserId OR
u.Id = c.PostId
WHERE u.DisplayName = 'alex'
Run Code Online (Sandbox Code Playgroud)
Users表上唯一的索引是 ID 上的聚集索引。
该Comments表具有以下非聚集索引以及 ID 上的聚集索引:
CREATE INDEX IX_UserID ON Comments
(
UserID,
PostID
)
CREATE INDEX IX_PostID ON Comments
(
PostID,
UserID
)
Run Code Online (Sandbox Code Playgroud)
查询的估计计划在这里:
我可以看到优化器将做的第一件事是对用户表执行 CI 扫描以仅过滤那些用户 where DisplayName = Alex,有效地执行此操作:
SELECT *
FROM Users u
WHERE u.DisplayName = 'alex'
ORDER BY Id
Run Code Online (Sandbox Code Playgroud)
并检索结果如下:
然后它会扫描评论 CI 并针对每一行,查看该行是否满足谓词
u.Id = c.UserId OR u.Id = c.PostId
Run Code Online (Sandbox Code Playgroud)
尽管有两个索引,但仍会执行此 CI 扫描。
如果优化器对上面 Comments 表中的每个索引进行单独的查找并将它们连接在一起,不是更有效吗?
如果我想象一下它会是什么样子,在上面的屏幕截图中,我们可以看到用户 CI 扫描的第一个结果是 ID 420
我可以想象IX_UserID使用索引的样子
SELECT UserID,
PostID
FROM Comments
ORDER BY UserID,
PostID
Run Code Online (Sandbox Code Playgroud)
因此,如果我查找用户 ID 420 的行作为索引查找将:
对于每一行 where UserID = 420,我可以查看 u.Id = c.UserId OR u.Id = c.PostId它们是否都匹配u.Id = c.UserId我们谓词的一部分,
因此,对于索引查找的第二部分,我们可以通过索引进行查找,IX_PostID其可视化如下:
SELECT PostID,
UserID
FROM Comments
ORDER BY PostID,
UserID
Run Code Online (Sandbox Code Playgroud)
如果我寻求发布 ID 420,我什么也看不到:
所以我们然后回到 CI 扫描的结果,移动到下一行(userId 447)并重复这个过程。
我上面描述的行为可以在WHERE子句中使用:
SELECT UserID,
PostID
FROM Comments
WHERE UserID = 420 OR PostID = 420
ORDER BY UserID,
PostID
Run Code Online (Sandbox Code Playgroud)
因此,我的问题是,为什么子句中的OR条件JOIN不能对适当的索引执行索引查找?
而不是专注于如何改进这样的查询,这是其他答案正在做的事情,我将尝试回答被问到的问题:为什么优化器不会产生像你所描述的那样的计划(扫描 Users 表,然后查找 Comments 表上的两个索引)。
这是您的原始查询(请注意,我MAXDOP 2只是为了模拟我在您的执行计划中看到的内容):
SELECT *
FROM Users u
LEFT JOIN Comments c
ON u.Id = c.UserId OR
u.Id = c.PostId
WHERE u.DisplayName = 'alex'
OPTION (MAXDOP 2);
Run Code Online (Sandbox Code Playgroud)
和计划:
dbo.Users使用残差谓词扫描以仅获取“alex”用户dbo.Comments表并过滤连接运算符中的匹配项获得您想要的计划的一种尝试是尝试在桌子上强制搜索dbo.Comments:
SELECT *
FROM Users u
LEFT JOIN Comments c WITH (FORCESEEK)
ON u.Id = c.UserId OR
u.Id = c.PostId
WHERE u.DisplayName = 'alex'
OPTION (MAXDOP 2);
Run Code Online (Sandbox Code Playgroud)
计划是这样的:
dbo.Users表(使用残差谓词只获取名为“alex”的用户),所以答案是优化器绝对有能力产生这样的计划。而且它似乎不是基于成本的决定(寻找计划看起来便宜得多)。
我最好的猜测是,这只是优化器探索过程中的某种限制——它似乎不赞成将带有 or 子句的左连接转换为应用。在这种特殊情况下,这真的很不幸,因为扫描计划(在我的机器上查询需要 45 秒)与应用计划(不到 1 秒)的性能很差。
旁注:您可以使用未记录的跟踪标志 8726 覆盖不利于索引联合计划的启发式方法。有关该方面的其他详细信息,请参阅https://dba.stackexchange.com/a/23779!
正如 Rob Farley 很有帮助地指出的那样,APPLY直接使用(也可能与 a 一起使用UNION)是获得您正在寻找的计划的更好方法 - 这两种方法都会产生该计划(FORCESEEK版本)的“更好”版本。我会说“ ORin a JOIN”是一种已知的反模式,应该避免使用,因为优化器似乎并没有直接支持这种类型的查询。
| 归档时间: |
|
| 查看次数: |
289 次 |
| 最近记录: |