在子选择中,使用 GetDate 而不是 @variable 会导致更糟糕的执行计划

Vla*_*sak 3 sql-server execution-plan

执行计划

这是现实生活问题的简化版本。GetDate在子选择中使用时,计划更糟且不同。2个查询:

select * , (select count(1) 
            from hamsfa.customer as sub 
            where sub.tstamp_nosync > @old 
              and sub.cus_id > main.cus_id) as x 
from hamsfa.customer as main ;


select * , (select count(1) 
            from hamsfa.customer as sub 
            where sub.tstamp_nosync > dateadd(year, -100, getdate())
              and sub.cus_id > main.cus_id) as x 
from hamsfa.customer as main ;
Run Code Online (Sandbox Code Playgroud)

我不完全理解的是为什么。

变量应该按我的理解变差。

还有另一个查询,这导致了几乎 700% 的差异(1s 与 7s)。

所以问题是:为什么一个变量会产生更好的计划,即使根据我的经验它应该更糟?

编辑:统计数据是最新且正确的

Pau*_*ite 7

当您使用该变量时,SQL Server 会猜测tstamp_nosync谓词的选择性 。对于>操作员,猜测是 30%。该表包含 3052 行,因此估计猜测 3052 = 915.6 行中的 30% 将通过该测试。

当您使用该dateadd(year, -100, getdate())表达式时,SQL Server 可以对符合该谓词的行数进行更准确的估计。

您的 SQL Server 2012 版本没有Actual Rows Read 属性,因此实际匹配的行数在您的第二个计划中不可用。该功能是在 SQL Server 2012 的 Service Pack 3 中添加的(出于某种原因,您仍在使用 Service Pack 1)。

据我所知,对dateadd表达式的更好估计是所有行都符合条件。这从第一个计划中的 Eager Index Spool 的输出中可以明显看出。3052 行使用tstamp_nosync谓词测试 3052 次(嵌套循环连接的每次迭代一次),并且全部通过。结果是总共 3052 * 3052 = 9,314,704 行。这显示为线轴中的实际行数:

Eager 线轴属性

使用变量会导致更糟糕的执行计划的普遍预期是合理的,因为具体的估计通常比完全猜测更准确;但是,sub.cus_id > main.cus_id查询中的另一个谓词将始终导致 30% 的猜测。

对于变量查询,两个谓词都是用 30% 的猜测估计的。组合估计为 3052 * 30% * 30% = 274.68 行(见于流聚合的输入):

从过滤器到流聚合的估计行数

对于dateadd查询,有一个 30% 的猜测(寻求谓词)和一个 100% 的选择性估计(残差谓词),如前所述,给出了 915.6 行的组合估计。该估计值显示在聚集索引搜索的输出中:

聚集索引查找属性

估计的差异解释了优化器的不同计划选择。

为可变案例估计的行越少,估计成本就越低。出于类似的原因,更好的估计成本更高。然而,由于cus_id谓词中涉及的猜测,这两个计划都基于不准确的估计。

观察到的性能方面,哪个计划执行得更好,而取决于您当地环境的细节。变量计划扫描源表一次并在tempdb 中构建索引临时表。非变量计划每次都通过搜索(猜测 30% 的选择性)和剩余谓词(计算的 100% 选择性)简单地访问基表。在实践中哪个表现更好取决于多种因素。

给定两个谓词的正确信息,优化器更有可能生成在实践中更好的计划。

非常注意数据类型通常是值得的。datetime2(4)在您的执行计划中有一个隐式转换,因为这是列的类型,而变量是别的东西(datetime也许)。dateadd表达式的结果也是datetime,也需要隐式转换。在这种特定情况下,这种转换似乎并不重要,但通常很重要。例如,请参阅:

性能惊喜和假设:DATEADD() Aaron Bertrand。