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先読み読み取り0SQL 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先読み読み取り0SQL 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先読み読み取り0SQL 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先読み読み取り0SQL 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先読み読み取り0SQL 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;