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

Eri*_*ing 16 sql-server optimization

沙袋

虽然其高质量博客Posts®工作,我碰到了一些优化的行为,我发现真的真气有趣。我没有立即得到解释,至少不是我满意的解释,所以我把它放在这里,以防有人聪明出现。

如果您想继续学习,可以在此处获取Stack Overflow 数据转储的 2013 版本。我正在使用 Comments 表,上面有一个额外的索引。

CREATE INDEX [ix_ennui] ON [dbo].[Comments] ( [UserId], [Score] DESC );
Run Code Online (Sandbox Code Playgroud)

查询一

当我像这样查询表时,我得到了一个奇怪的查询计划

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;
Run Code Online (Sandbox Code Playgroud)

坚果

Score 上的 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;
Run Code Online (Sandbox Code Playgroud)

查询计划改变了,过了,运行速度更快,没有溢出到磁盘。它们都产生相同的结果,在非聚集索引扫描中使用谓词。

坚果

坚果

查询三

这相当于像这样编写查询:

SELECT   TOP 101
         c.UserId, c.Text, c.Score
FROM     dbo.Comments AS c
WHERE c.Score >= 500
ORDER BY c.Score DESC;
Run Code Online (Sandbox Code Playgroud)

查询四

使用派生表获得与初始 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;
Run Code Online (Sandbox Code Playgroud)

事情变得更奇怪时...

我更改查询以对数据进行升序排序,并将过滤器更改为<=.

为了避免这个问题太长,我要把所有的东西放在一起。

查询

--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;
Run Code Online (Sandbox Code Playgroud)

计划

计划链接

坚果

请注意,这些查询都没有利用非聚集索引——这里唯一改变的是过滤运算符的位置。在任何情况下都不会将谓词推送到索引访问。

出现一个问题!

是否有理由可以在某些情况下推送 SARGable 谓词而在其他情况下不能推送?按降序排序的查询中的差异很有趣,但这些查询之间的差异与升序排序的差异很奇怪。

对于任何感兴趣的人,这里是只有一个索引的计划Score

Pau*_*ite 12

这里有几个问题。

将谓词推过去 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;
Run Code Online (Sandbox Code Playgroud)

...在过滤器中生成一个带有明显未推送谓词的计划:

由于行目标而延迟过滤器

请注意,Sort 估计会产生 101 行。这是Top添加的行目标的效果。这会影响 Sort 和 Filter 的估计成本,足以使它看起来像是更便宜的选择。该计划的估计成本为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'));
Run Code Online (Sandbox Code Playgroud)

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

没有行目标的计划

谓词已作为剩余的不可sargable谓词被推入扫描,整个计划的成本为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;
Run Code Online (Sandbox Code Playgroud)

现在预计谓词非常有选择性,因此优化器选择推送谓词使用非聚集索引进行查找:

选择性谓词

同样,优化器考虑了多种替代方案,并像往常一样选择了它作为显然最便宜的选择。