标量函数改变前一个运算符的估计

Fru*_*aun 4 sql-server execution-plan functions cardinality-estimates

请有人帮助我理解为什么标量函数的存在会改变先前索引扫描的估计?

我正在使用 StackOverflow2013 数据库的副本。SQL Server 2019(兼容模式100)

为了演示这个问题,我创建了一个简单的函数,它检查 User.DisplayName 中是否有“s”:

CREATE FUNCTION dbo.IsUserS (@DisplayName nvarchar(80))

RETURNS bit
AS
BEGIN
    RETURN CHARINDEX('S', @DisplayName)
END
 
Run Code Online (Sandbox Code Playgroud)

这是使用该函数的查询:

SELECT  U.ID,
    U.[DisplayName],
    U.[Reputation]
FROM    [dbo].[Users] AS [U]
WHERE   [U].[CreationDate] > '20100101'
AND [U].[Reputation] > 100
AND dbo.[IsUserS](u.[DisplayName]) = 1
Run Code Online (Sandbox Code Playgroud)

我正在将此查询的计划与完全相同的查询进行比较,但没有函数调用。以下是计划:

https://www.brentozar.com/pastetheplan/?id=ByLy1LlNi

在这两个计划中,我们都从聚集索引扫描开始。对于底部计划(没有过滤器的计划),扫描运算符的估计值与实际值很接近。对于带有过滤器的计划,估计值相差很大(实际行数的 36%)。

我的问题是,为什么 UDF 的存在会改变先前索引扫描的估计?

请注意,我正在 Compat' 模式 100 下运行。如果我翻转到 150,估计值与实际值实际上会更差,除非我打开旧基数估计器(在这种情况下,它与 100 相同,所以仍然很糟糕)。

Pau*_*ite 12

这是优化后重写的结果,它将合适的谓词作为剩余谓词推送到搜索或扫描运算符中。

优化器的估计基于此重写之前的树,其中所有谓词都显示为单独的过滤器。您可以通过启用未记录的跟踪标志 9130 来查看此计划,该标志可防止下推重写。

当重写发生时,它会将属性从过滤器复制到搜索或扫描,作为树调整的一部分。这解释了您看到的不同估计。

标量函数无法下推,但CreationDateReputation上的谓词可以。重写不够聪明,无法重新进行估计以反映只有某些谓词可以成功下推的事实。

它看起来很奇怪,但它并没有影响优化器的选择,因为这些都是在优化后工作完成之前做出的。将谓词下推是一种物理优化。在查找或扫描期间评估条件比将行传递给单独的 Filter 运算符更便宜。


使用标量函数的估计计划,由于跟踪标志 9130,谓词未下推:

TF 9130 的预计计划

使用标量函数和下推谓词的估计计划:

不含 TF 9130 的预计计划

过滤器的全部效果已被复制到扫描中,因为三个谓词中的两个在那里进行评估。这给人一种误导性的印象,即过滤器不会消除任何行,但事实并非如此,也不是优化器的想法。


标量函数并不是唯一阻止谓词下推的因素。大数据类型和sql_variant表达式也无法在查找或扫描中进行计算。例如:

SELECT
    U.ID,
    U.[DisplayName],
    U.[Reputation]
FROM dbo.Users AS U
WHERE 
    U.CreationDate > '20100101'
    AND U.Reputation > 100
    AND CHARINDEX(N'S', CONVERT(nvarchar(max), U.DisplayName)) = 1;
Run Code Online (Sandbox Code Playgroud)

LOB 类型上的谓词无法下推,因此您也会在那里看到相同的明显不一致的估计。