sql-server – SQL Server是否缓存多语句表值函数的结果?

多语句表值函数将其结果返回到表变量中.

这些结果是否曾被重复使用,或者每次调用时都会对函数进行全面评估?

解决方法

多语句表值函数(msTVF)的结果从不在语句(或连接)之间缓存或重用,但有几种方法可以在同一语句中重用msTVF结果.在这种程度上,每次调用msTVF时都不一定要重新填充.

示例msTVF

这(故意低效)msTVF返回指定的整数范围,每行有一个时间戳:

IF OBJECT_ID(N'dbo.IntegerRange','TF') IS NOT NULL
    DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer,@To integer)
RETURNS @T table 
(
    n integer PRIMARY KEY,ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
    WHILE @From <= @To
    BEGIN
        INSERT @T (n)
        VALUES (@From);

        SET @From = @From + 1;
    END;
    RETURN;
END;

静态表变量

如果函数调用的所有参数都是常量(或运行时常量),则执行计划将填充表变量result一次.计划的其余部分可以多次访问表变量.可以从执行计划中识别表变量的静态特性.例如:

SELECT
    IR.n,IR.ts 
FROM dbo.IntegerRange(1,5) AS IR
ORDER BY
    IR.n;

返回类似于的结果:

执行计划是:

Sequence运算符首先调用Table Valued Function运算符,该运算符填充表变量(注意此运算符不返回任何行).接下来,Sequence调用其第二个输入,该输入返回表变量的内容(在这种情况下使用Clustered Index Scan).

计划使用“静态”表变量结果的赠品是序列下面的表值函数运算符 – 表变量需要在计划的其余部分开始之前完全填充一次.

多次访问

显示多次访问的表变量结果,我们将使用第二个表,其中行的编号从1到5:

IF OBJECT_ID(N'dbo.T','U') IS NOT NULL
    DROP TABLE dbo.T;

CREATE TABLE dbo.T (i integer NOT NULL);

INSERT dbo.T (i) 
VALUES (1),(2),(3),(4),(5);

以及将此表连接到我们的函数的新查询(这同样可以写为APPLY):

SELECT T.i,IR.n,IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1,5) AS IR
    ON IR.n = T.i;

结果是:

执行计划:

和以前一样,Sequence首先填充表变量msTVF结果.接下来,嵌套循环用于将表T中的每一行连接到msTVF结果中的一行.由于函数定义包含对表变量的有用索引,因此可以使用索引查找.

关键点是当msTVF的参数是常量(包括变量和参数)或被执行引擎视为语句的运行时常量时,该计划将为msTVF表变量结果提供两个独立的运算符:一个填充桌子;另一个访问结果,可能多次访问表,并可能使用函数定义中声明的索引.

相关参数和非常数参数

要在使用相关参数(外部引用)或非常量函数参数时突出显示差异,我们将更改表T的内容,以便该函数还有更多工作要做

TruncATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50001),(50002),(50003),(50004),(50005);

以下修改后的查询现在在其中一个函数参数中使用对表T的外部引用:

SELECT T.i,IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1,T.i) AS IR
WHERE IR.n = T.i;

查询大约需要8秒才能返回结果,如:

注意列ts中行之间的时差. WHERE子句限制了一个合理大小的输出的最终结果,但是低效的函数仍然需要一段时间来填充表变量,其中包含50,000多行(取决于表T中的i的相关值).

执行计划是:

注意缺少Sequence运算符.现在,有一个Table Valued Function运算符填充表变量并在嵌套循环join的每次迭代中返回其行.

需要明确的是:表T中只有5行,表值函数运算符运行5次.它在第一次迭代时生成50,001行,在第二次迭代时生成50,002行……依此类推.表变量在迭代之间被“抛弃”(截断),因此五个调用中的每一个都是完整填充.这就是它如此缓慢的原因,并且每行大约需要在结果中出现相同的时间.

附注:

当然,上面的场景是故意设计的,以显示当msTVF在每次迭代中填充许多行时性能有多差.

上述代码的合理实现会将msTVF参数设置为i,并删除冗余的WHERE子句.表变量仍将在每次迭代时被截断并重新填充,但每次只有一行.

我们还可以从T中获取最小和最大i值,并将它们存储在前一步骤中的变量中.使用变量而不是相关参数调用函数将允许使用’static’表变量模式,如前所述.

缓存未更改的相关参数

返回再次解决原始问题,在不能使用Sequence静态模式的情况下,如果自嵌套循环连接的先前迭代以来没有任何相关参数发生更改,则sql Server可以避免截断并重新填充msTVF表变量.

为了证明这一点,我们将用五个相同的i值替换T的内容

TruncATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50005),(50005),(50005);

再次使用相关参数的查询

SELECT T.i,T.i) AS IR
WHERE IR.n = T.i;

这次结果出现在1.5秒左右:

请注意每行上的相同时间戳.表变量中的缓存结果重用于后续迭代,其中相关值i不变.重复使用结果比每次插入50,005行快得多.

执行计划看起来与以前非常相似:

关键区别在于Table Valued Function运算符的Actual Rebinds和Actual Rewinds属性

当相关参数不变时,sql Server可以重放(倒回)表变量中的当前结果.当关联更改时,sql Server必须截断并重新填充表变量(重新绑定).一次重新绑定发生在第一次迭代;由于T.i的值不变,所以后续四次迭代都是倒带.

相关文章

SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_n...
if not exists(select name from syscolumns where name=&am...
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_n...
要在 SQL Server 2019 中设置定时自动重启,可以使用 Window...
您收到的错误消息表明数据库 &#39;EastRiver&#39; 的...
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标...