什么时候可以将SARGable谓词推送到CTE或派生表中?


15

沙袋

在从事高质量博客文章®的工作时,我遇到了一些优化器行为,我发现这确实令人非常恼火。我没有立即作出解释,至少没有一个我满意的解释,因此我将其放在此处,以防有人出现。

如果要继续学习,可以在此处获取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

Answers:


11

这里有一些问题。

推动谓词过去 TOP

当前TOP,即使在有限的情况下,优化器也无法将谓词推送到a之上*。此限制说明了谓词的范围比更大的问题中所有查询的行为TOP

解决方法是手动执行重写。基本问题与将谓词推过窗口函数的情况相似,但没有类似的专门规则SelOnSeqPrj

我个人认为,像这样的探索规则SelOnTop仍然没有得到实施,因为人们故意编写查询,TOP以提供一种“优化围栏”。

*通常,这意味着谓词应出现在与ORDER BY关联的子句中TOP,并且任何不等式的方向应与排序的方向一致。转换还需要考虑SQL Server中NULL的排序行为。总体而言,这些局限性可能意味着这种转换在实践中通常将不足以证明其他勘探工作的合理性。

成本核算问题

由于Score列中值的分布(<= 500的行多于> = 500,所以)中问题的其余执行计划可以解释为基于成本的选择,并且由引入行目标的效果TOP

例如,查询:

--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;

...产生一个带有未过滤谓词的计划,该计划在Filter中:

由于行目标而导致延迟过滤

请注意,排序估计会产生101行。这是“顶部”添加的行目标的效果。这会影响“排序”和“过滤器”的估计成本,足以使其看起来像是较便宜的选择。该计划的估计成本为2401.39单位。

如果我们通过查询提示禁用行目标:

--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
OPTION (USE HINT ('DISABLE_OPTIMIZER_ROWGOAL'));

...产生的执行计划是:

没有行目标的计划

该谓词已作为残存的不可燃谓词被推入扫描,整个计划的成本为2402.32个单位。

请注意,<= 500谓词不应过滤掉任何行。如果您选择了一个较小的数字(如)<= 50,则无论行目标效果如何,优化器都将首选推式谓词计划。

对于带有Score DESCScore >= 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 DESC;

现在,预期谓词具有很高的选择性,因此优化程序选择推送谓词并将非聚集索引与查找配合使用:

选择谓词

再次,优化程序考虑了多种选择,并像往常一样选择了它作为最便宜的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.