Han*_*non 18 sql-server sql-server-2012 set-returning-functions
比较关于回文问题的一些答案(仅限 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
Run Code Online (Sandbox Code Playgroud)
内联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
Run Code Online (Sandbox Code Playgroud)
Numbers
上述函数中的表定义为:
CREATE TABLE dbo.Numbers
(
Number INT NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
注意: numbers 表没有任何索引,也没有主键,包含 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;
Run Code Online (Sandbox Code Playgroud)
在我的测试系统上,上述INSERT
结果导致#Words
表中插入了 16,900 行。
为了测试这两种变体,我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;
Run Code Online (Sandbox Code Playgroud)
我预计该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)
);
Run Code Online (Sandbox Code Playgroud)
正如@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, 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;
Run Code Online (Sandbox Code Playgroud)
Mar*_*ith 12
您的数字表是一个堆,每次都可能被完全扫描。
添加集群主键Number
并尝试以下forceseek
提示以获得所需的搜索。
据我所知,这个提示是需要的,因为 SQL Server 只是估计表的 27% 将匹配谓词(30% 为 the <=
,减少到 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
Run Code Online (Sandbox Code Playgroud)
有了这些变化,它为我飞行(需要 228 毫秒)