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


Answers:


23

多语句表值函数(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;

静态表变量

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

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

返回类似于以下内容的结果:

简单的结果

执行计划是:

简单的执行计划

Sequence运算符首先调用Table Valued Function运算符,该运算符填充表变量(注意,此运算符不返回任何行)。接下来,该序列调用其第二个输入,该输入返回表变量的内容(在这种情况下,使用聚簇索引扫描)。

计划正在使用“静态”表变量的结果是在序列下方的表值函数运算符-在计划的其余部分可以执行之前,表变量需要完全填充一次。

多次访问

为了显示多次访问该表变量的结果,我们将使用第二个表,行的编号从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.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

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

相关结果

注意column中各行之间的时差ts。该WHERE子句限制了合理大小的输出的最终结果,但是效率低下的函数仍然需要花费一些时间才能向表变量中填充50,000多行(取决于ifrom table 的相关值T)。

执行计划是:

相关执行计划

注意缺少序列运算符。现在,只有一个表值函数运算符可填充表变量,并在嵌套循环联接的每次迭代中返回其行。

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

旁注:

自然,上面的方案是故意设计的,以显示当msTVF在每次迭代中填充许多行时,性能可能会很差。

明智实现上面的代码将设置两个 msTVF参数i,并去除多余的WHERE子句。在每次迭代中,表变量仍将被截断并重新填充,但每次仅一行。

我们还可以iT上一个步骤中获取最小值和最大值并将它们存储在变量中。如前所述,使用变量而不是相关参数来调用函数将允许使用“静态”表变量模式。

缓存不变的相关参数

回到原来的问题,即不能使用Sequence静态模式,如果自嵌套循环连接的上一次迭代以来相关参数均未更改,则SQL Server 可以避免截断和重新填充msTVF表变量。

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

TRUNCATE TABLE dbo.T;

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

再次使用相关参数查询:

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

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

相同的行结果

注意每行上的时间戳相同。table变量中的缓存结果将重新用于相关值i未更改的后续迭代。重用结果比每次插入50,005行要快得多。

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

规划相同的行

关键区别是在实际重新绑定实际倒带表值函数算子性质:

操作员属性

当相关参数不变时,SQL Server可以在表变量中重播(快退)当前结果。当相关性更改时,SQL Server必须截断并重新填充表变量(重新绑定)。一个重新绑定发生在第一次迭代中。由于的值T.i未更改,因此所有四个后续迭代均倒带。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.