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 DESC和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 DESC;
Run Code Online (Sandbox Code Playgroud)
现在预计谓词非常有选择性,因此优化器选择推送谓词并使用非聚集索引进行查找:
同样,优化器考虑了多种替代方案,并像往常一样选择了它作为显然最便宜的选择。