为什么 SQL Server 能够准确跟踪某些多语句表值函数查询计划的时间,而其他查询计划则不然?

Eri*_*ing 11 sql-server execution-plan functions

设置

在本演示中,我使用的是2013 版 Stack Overflow 数据库和 SQL Server 2022 CTP2,但回溯到 SQL Server 2017 也是有效的,这是我想检查的最早版本。

功能一

对于此函数,SQL Server 跟踪该函数所花费的执行时间:

CREATE OR ALTER FUNCTION
    dbo.ScoreStats
(
    @UserId int
)
RETURNS
    @out table
    (
        TotalScore bigint
    )
WITH SCHEMABINDING
AS 
BEGIN

    INSERT
        @out
    (
        TotalScore
    )
    SELECT
        TotalScore = 
            SUM(x.Score)
    FROM 
    (
        SELECT
            Score = 
                SUM(p.Score)
        FROM dbo.Posts AS p
        WHERE p.OwnerUserId = @UserId

        UNION ALL

        SELECT
            Score = 
                SUM(c.Score)
        FROM dbo.Comments AS c
        WHERE c.UserId = @UserId    
    ) AS x;

    RETURN;

END;
Run Code Online (Sandbox Code Playgroud)

这是查询和执行计划:

SELECT
    u.DisplayName,
    TotalScore = 
        (
            SELECT
                ss.TotalScore
            FROM dbo.ScoreStats(u.Id) AS ss
        )
FROM dbo.Users AS u
WHERE u.Reputation >= 1000000;
Run Code Online (Sandbox Code Playgroud)

坚果

您可以看到,在查询计划和查询时间统计属性中,时间都得到了准确的跟踪。

功能二

这是第二个函数,但这种情况不会发生:

CREATE OR ALTER FUNCTION
    dbo.VoteStats()
RETURNS
    @out table
    (
        PostId int,
        UpVotes int,
        DownVotes int,
        UpMultipier AS 
             UpVotes * 2
    )
WITH SCHEMABINDING
AS 
BEGIN

    INSERT
        @out
    (
        PostId,
        UpVotes,
        DownVotes
    )
    SELECT
        v.PostId,
        UpVotes = 
            SUM
            (
                CASE v.VoteTypeId
                     WHEN 2
                     THEN 1
                     ELSE 0
                END
            ),
        DownVotes = 
            SUM
            (
                CASE v.VoteTypeId
                     WHEN 3
                     THEN 1
                     ELSE 0
                END
            )
    FROM dbo.Votes AS v
    GROUP BY 
        v.PostId;

    RETURN;

END;
Run Code Online (Sandbox Code Playgroud)

这是查询和执行计划:

SELECT TOP (100)
     p.Id,
     vs.UpVotes,
     vs.DownVotes
FROM dbo.VoteStats() AS vs
JOIN dbo.Posts AS p
    ON vs.PostId = p.Id
WHERE vs.DownVotes > vs.UpMultipier
AND   p.CommunityOwnedDate IS NULL
AND   p.ClosedDate IS NULL
ORDER BY vs.UpVotes DESC;
Run Code Online (Sandbox Code Playgroud)

坚果

在此查询中,时间不是在图形执行计划中准确跟踪,而是在“查询时间统计”属性中跟踪。

MAXDOP 1 时的功能二

即使强制串行,也无法准确跟踪时间:

SELECT TOP (100)
     p.Id,
     vs.UpVotes,
     vs.DownVotes
FROM dbo.VoteStats() AS vs
JOIN dbo.Posts AS p
    ON vs.PostId = p.Id
WHERE vs.DownVotes > vs.UpMultipier
AND   p.CommunityOwnedDate IS NULL
AND   p.ClosedDate IS NULL
ORDER BY vs.UpVotes DESC
OPTION(MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)

坚果

问题

回到当前的问题:为什么一个查询计划可以准确地跟踪时间,而另一个查询计划则不能?

Pau*_*ite 14

这是使用交错 TVF 执行的结果。

交错执行改变了单个查询执行的优化和执行阶段之间的单向边界,并使计划能够根据修改后的基数估计进行调整。在优化期间,如果我们遇到交错执行的候选对象,即当前的多语句表值函数(MSTVF),我们将暂停优化,执行适用的子树,捕获准确的基数估计,然后恢复下游操作的优化。

您的第一个示例不符合交错执行的条件,但第二个示例符合交错执行的条件。第二个示例计划的根节点具有以下属性:

包含候选人

第二个示例中的 TVF 群体节点具有:

是交错执行的

运行测试查询并提示禁用该功能:

SELECT TOP (1) VS.* 
FROM dbo.VoteStats() AS VS
OPTION (USE HINT ('DISABLE_INTERLEAVED_EXECUTION_TVF'));
Run Code Online (Sandbox Code Playgroud)

给出一个计划,包括填充 TVF 的时间:

计划与时间安排


此问题仅在首次执行符合交错 TVF 执行资格的语句时发生。SQL Server 执行计划的 TVF 填充部分,以便在查询优化期间获得准确的基数估计。在获得该信息之前,不会对计划的其余部分进行编译和优化。

编译完成后,SQL Server 不会重复第一次执行时填充表变量的工作,因为这会重复已经完成的工作(在优化期间)。不幸的是,在运行时跳过表填充意味着表变量填充的性能信息无法以通常的方式获得。

在后续执行中(重用计划的情况),SQL Server确实将表变量填充步骤作为常规查询执行的一部分运行,因此运行时性能数字会按预期显示在 showplan 输出中。

如果您再次运行第二个示例,重新使用缓存的计划,您将看到完整的运行时性能信息。

注意:此行为与智能查询处理的交错 TVF 执行功能特别相关。这不是 TVF 正常缓存行为的结果,正如我在自我回答的 Q&A SQL Server 是否缓存多语句表值函数的结果中所解释的那样?