沙袋
在从事高质量博客文章®的工作时,我遇到了一些优化器行为,我发现这确实令人非常恼火。我没有立即作出解释,至少没有一个我满意的解释,因此我将其放在此处,以防有人出现。
如果要继续学习,可以在此处获取2013年版本的Stack Overflow数据转储。我正在使用注释表,上面还有一个索引。
CREATE INDEX [ix_ennui] ON [dbo].[Comments] ( [UserId], [Score] DESC );
查询一
当我像这样查询表时,我得到一个奇怪的查询计划。
WITH x
AS
(
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score DESC
)
SELECT *
FROM x
WHERE x.Score >= 500;
分数的SARGable谓词未在CTE中推送。在计划的后期,它位于过滤器运算符中。
我觉得很奇怪,因为ORDER BY
和过滤器位于同一列。
查询二
如果我更改查询,它将被推送。
WITH x
AS
(
SELECT c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
)
SELECT TOP 101 *
FROM x
WHERE x.Score >= 500
ORDER BY x.Score DESC;
该查询计划改变了,过了,运行速度更快,没有溢出到磁盘。它们都产生相同的结果,而谓词位于非聚集索引扫描中。
查询三
这等效于编写查询,如下所示:
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
WHERE c.Score >= 500
ORDER BY c.Score DESC;
查询四
使用派生表将获得与初始CTE查询相同的“错误”查询计划
SELECT *
FROM ( SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score DESC ) AS x
WHERE x.Score >= 500;
当...变得更奇怪
我将查询更改为数据升序,并将过滤器更改为<=
。
为了避免使这个问题过于冗长,我将把所有内容放在一起。
查询
--Derived table
SELECT *
FROM ( SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score ASC ) AS x
WHERE x.Score <= 500;
--TOP inside CTE
WITH x
AS
(
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score ASC
)
SELECT *
FROM x
WHERE x.Score <= 500;
--Written normally
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
WHERE c.Score <= 500
ORDER BY c.Score ASC;
--TOP outside CTE
WITH x
AS
(
SELECT c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
)
SELECT TOP 101 *
FROM x
WHERE x.Score <= 500
ORDER BY x.Score ASC;
计划
计划链接。
请注意,这些查询均未利用非聚集索引-唯一改变的是过滤器运算符的位置。决不会将谓词推送到索引访问。
出现问题!
是否存在可以在某些情况下而不是在其他情况下推送SARGable谓词的原因?查询中以降序排列的差异很有趣,但是它们之间的差异与奇异的上升之间的差异有关。
对于任何有兴趣的人,以下是仅带有索引的计划Score
: