マルチステートメントTVFとインラインTVFパフォーマンス


18

Palindromeの質問に対する回答の一部を比較すると(回答を削除したため、1万人以上のユーザーのみ)、紛らわしい結果が得られています。

複数のステートメント、スキーマにバインドされたTVFを提案しました。これは、標準関数を実行するよりも高速だと思いました。また、以下に示すように、マルチステートメントTVFは「インライン化」されるという印象もありましたが、その点では間違っています。この質問は、TVFのこれら2つのスタイルのパフォーマンスの違いに関するものです。まず、コードを確認する必要があります。

次に、複数ステートメントの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結果、#Wordsテーブルに16,900行が挿入されます。

2つのバリエーションをテスト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。
テーブル「作業テーブル」。スキャンカウント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行が影響を受けます)
テーブル '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時間= 328ミリ秒、経過時間= 424ミリ秒。

CROSS APPLY dbo.InlineIsPalindrome(w.Word) p

(17424行が影響を受けます)
テーブル「番号」。スキャンカウント1、論理読み取り237100、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル「作業テーブル」。スキャンカウント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、Developer Editionでこれをテストしています。

主キーとクラスター化インデックスを使用した、私の数値テーブルの定義は次のとおりです。

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;

コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
ポールホワイトはGoFundMonicaを言う

回答:


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

これらの変更が適所にあると、私のために飛びます(228msかかります)

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.