回文の質問 (回答を削除したため、10,000人以上のユーザーのみ)の回答の一部を比較すると、混乱する結果になります。
私は multi-statement、schema-bound 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
により、16,900行が_#Words
_テーブルに挿入されます。
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。
テーブル「ワークテーブル」。スキャンカウント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 ms、経過時間= 2022 ms。
SQL Serverの解析およびコンパイル時間:
CPU時間= 0 ms、経過時間= 0 ms。
インラインTVF:
テーブル「番号」。スキャンカウント1、論理読み取り1272030、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル「ワークテーブル」。スキャンカウント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 ms、経過時間= 139415 ms。
SQL Serverの解析およびコンパイル時間:
CPU時間= 0 ms、経過時間= 0 ms。
実行計画は次のようになります。
この場合、インラインバリアントがマルチステートメントバリアントよりもはるかに遅いのはなぜですか?
@AaronBertrandによるコメントに応じて、CTEによって返される行を入力Wordの長さに一致するように制限するように_dbo.InlineIsPalindrome
_関数を変更しました。
_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行が影響を受けました)
テーブル「ワークテーブル」。スキャンカウント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;
_
数値テーブルはヒープであり、毎回完全にスキャンされる可能性があります。
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かかります)