多语句TVF与串联TVF性能


18

比较回文问题上的一些答案(仅限10k以上的用户,因为我删除了答案),结果令人困惑。

我提出了一个多语句,受模式约束的TVF,我认为它比运行标准功能要快。我也有一种印象,即多语句TVF将被“内联”,尽管在这一点上我是错的,如下所示。这个问题是关于这两种样式的TVF的性能差异。首先,您需要查看代码。

这是多语句TVF:

IF OBJECT_ID('dbo.IsPalindrome') IS NOT NULL
DROP FUNCTION dbo.IsPalindrome;
GO
CREATE FUNCTION dbo.IsPalindrome
(
    @Word NVARCHAR(500)
) 
RETURNS @t TABLE
(
    IsPalindrome BIT NOT NULL
)
WITH SCHEMABINDING
AS
BEGIN
    DECLARE @IsPalindrome BIT;
    DECLARE @LeftChunk NVARCHAR(250);
    DECLARE @RightChunk NVARCHAR(250);
    DECLARE @StrLen INT;
    DECLARE @Pos INT;
    SET @RightChunk = '';
    SET @IsPalindrome = 0;
    SET @StrLen = LEN(@Word) / 2;
    IF @StrLen % 2 = 1 SET @StrLen = @StrLen - 1;
    SET @Pos = LEN(@Word);
    SET @LeftChunk = LEFT(@Word, @StrLen);
    WHILE @Pos > (LEN(@Word) - @StrLen)
    BEGIN
        SET @RightChunk = @RightChunk + SUBSTRING(@Word, @Pos, 1)
        SET @Pos = @Pos - 1;
    END
    IF @LeftChunk = @RightChunk SET @IsPalindrome = 1;
    INSERT INTO @t VALUES (@IsPalindrome);
    RETURN
END
GO

嵌入式TVF:

IF OBJECT_ID('dbo.InlineIsPalindrome') IS NOT NULL
DROP FUNCTION dbo.InlineIsPalindrome;
GO
CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers
    )
    SELECT
      IsPalindrome =
        CASE
          WHEN EXISTS
          (
            SELECT N
            FROM Nums
            WHERE N <= L / 2
              AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
          )
          THEN 0
          ELSE 1
        END
    FROM
      (SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);
GO

Numbers上面函数中的表定义为:

CREATE TABLE dbo.Numbers
(
    Number INT NOT NULL 
);

注意:数字表没有任何索引,也没有主键,并且包含1,000,000行。

测试台临时表:

IF OBJECT_ID('tempdb.dbo.#Words') IS NOT NULL
DROP TABLE #Words;
GO
CREATE TABLE #Words 
(
    Word VARCHAR(500) NOT NULL
);

INSERT INTO #Words(Word) 
SELECT o.name + REVERSE(w.name)
FROM sys.objects o
CROSS APPLY (
    SELECT o.name
    FROM sys.objects o
) w;

在我的测试系统上,以上INSERT结果导致将16,900行插入#Words表中。

为了测试这两种变体,我SET STATISTICS IO, TIME ON;使用以下方法:

SELECT w.Word
    , p.IsPalindrome
FROM #Words w
    CROSS APPLY dbo.IsPalindrome(w.Word) p
ORDER BY w.Word;


SELECT w.Word
    , p.IsPalindrome
FROM #Words w
    CROSS APPLY dbo.InlineIsPalindrome(w.Word) p
ORDER BY w.Word;

我希望该InlineIsPalindrome版本的运行速度更快,但是以下结果不支持该假设。

多语句TVF:

表'#A1CE04C3'。扫描计数16896,逻辑读16900,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'Worktable'。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'#Words'。扫描计数1,逻辑读88,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

SQL Server执行时间:
CPU时间= 1700毫秒,经过的时间= 2022毫秒。
SQL Server解析和编译时间:
CPU时间= 0毫秒,经过的时间= 0毫秒。

嵌入式TVF:

表“数字”。扫描计数1,逻辑读1272030,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'Worktable'。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'#Words'。扫描计数1,逻辑读88,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

SQL Server执行时间:
CPU时间= 137874毫秒,经过的时间= 139415毫秒。
SQL Server解析和编译时间:
CPU时间= 0毫秒,经过的时间= 0毫秒。

执行计划如下:

在此处输入图片说明

在此处输入图片说明

为什么在这种情况下,内联变量比多语句变量要慢得多?

为了响应@AaronBertrand的评论,我修改了dbo.InlineIsPalindrome函数以限制CTE返回的行以匹配输入单词的长度:

CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers
      WHERE 
        number <= LEN(@Word)
    )
    SELECT
      IsPalindrome =
        CASE
          WHEN EXISTS
          (
            SELECT N
            FROM Nums
            WHERE N <= L / 2
              AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
          )
          THEN 0
          ELSE 1
        END
    FROM
      (SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);

正如@MartinSmith所建议的那样,我在表中添加了一个主键和聚集索引dbo.Numbers,这肯定会有所帮助,并且将更接近生产环境中的期望值。

现在,重新运行上述测试将得到以下统计信息:

CROSS APPLY dbo.IsPalindrome(w.Word) p

(受影响的17424行)
表'#B1104853'。扫描计数17420,逻辑读取17424,物理读取0,预读为0,lob逻辑读取0,lob物理读为0,lob预读为0。
表'Worktable'。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'#Words'。扫描计数1,逻辑读90,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

SQL Server执行时间:
CPU时间= 1763毫秒,经过的时间= 2192毫秒。

dbo.FunctionIsPalindrome(w.Word)

(受影响的17424行)
表“工作表”。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'#Words'。扫描计数1,逻辑读90,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

SQL Server执行时间:
CPU时间= 328毫秒,经过的时间= 424毫秒。

CROSS APPLY dbo.InlineIsPalindrome(w.Word) p

(受影响的17424行)
表“编号”。扫描计数1,逻辑读237100,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'Worktable'。扫描计数0,逻辑读0,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
表'#Words'。扫描计数1,逻辑读90,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。

SQL Server执行时间:
CPU时间= 17737毫秒,经过的时间= 17946毫秒。

我正在开发人员版本的SQL Server 2012 SP3 v11.0.6020上对此进行测试。

这是我的数字表的定义,其中包含主键和聚集索引:

CREATE TABLE dbo.Numbers
(
    Number INT NOT NULL 
        CONSTRAINT PK_Numbers
        PRIMARY KEY CLUSTERED
);

;WITH n AS
(
    SELECT v.n 
    FROM (
        VALUES (1) 
            ,(2) 
            ,(3) 
            ,(4) 
            ,(5) 
            ,(6) 
            ,(7) 
            ,(8) 
            ,(9) 
            ,(10)
        ) v(n)
)
INSERT INTO dbo.Numbers(Number)
SELECT ROW_NUMBER() OVER (ORDER BY n1.n)
FROM n n1
    , n n2
    , n n3
    , n n4
    , n n5
    , n n6;

评论不作进一步讨论;此对话已转移至聊天
保罗·怀特

Answers:


12

您的数字表是一个堆,每次都可能会被完全扫描。

在上面添加一个集群主键,Number并尝试以下forceseek提示并获得所需的搜索。

据我所知,此提示是必需的,因为SQL Server只是估计表的27%将与谓词匹配(的占30%,<=而由减少至27%<>)。因此,在找到匹配的行之前,它只需要读取3-4行即可退出半连接。因此,扫描选项的成本非常低廉。但是实际上,如果确实存在任何回文,那么就必须读取整个表,所以这不是一个好计划。

CREATE FUNCTION dbo.InlineIsPalindrome
(
    @Word NVARCHAR(500)
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN (
    WITH Nums AS
    (
      SELECT
        N = number
      FROM
        dbo.Numbers WITH(FORCESEEK)
    )
    SELECT
      IsPalindrome =
        CASE
          WHEN EXISTS
          (
            SELECT N
            FROM Nums
            WHERE N <= L / 2
              AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
          )
          THEN 0
          ELSE 1
        END
    FROM
      (SELECT LTRIM(RTRIM(@Word)), LEN(@Word)) AS v (S, L)
);
GO

有了这些更改,它就会为我飞翔(耗时228毫秒)

在此处输入图片说明

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.