比较回文问题上的一些答案(仅限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;