jmo*_*eno 4 performance sql-server sql-server-2008-r2 functions
我正在编写一个表值函数,调用该函数所需的时间是直接运行代码的 10 倍。我将此追溯到对 TVF 内的多行标量函数的调用。从 TVF 内部调用时,对标量函数的调用过慢。标量函数接受 3 个 int 参数并返回单个 int 结果。
什么会导致它在 TVF 中变慢?
首先,TVF 基本上是一种数据透视表,只返回一行,有 13 列。
标量函数是一个多行标量,它在两个表中的一个中查找一组匹配的键(即输入是 columnA,输出是 columnB,其中 input = columnA 和 active)。使用稍微更改的 where 子句再次搜索表 1,然后是表 2,最后是表 1。
用法最初是一个带有连接子句的 cte 查询:
With cte as ( select count(*) over (partition by c.c1) recs,
c.setId, c.c1, c.c2, n.name
From tbl c
Inner join tbl n on n.id = schma.scal(c.c1, c.c2, c.c3)
Where c.setId=@setId and c.endDt is null
)
Run Code Online (Sandbox Code Playgroud)
这个 cte 最初被调用了 11 次,从(大约一半的时间)返回到 3 行(使用像 c1=42 这样的 where 子句)。所以,我想,嘿,它被调用了很多,因为总共只有 4-15 行,而基表是 ~1,000,000,我将通过将值放入表中将其减少到只有 15 次调用变量,然后进行更新。这实际上并没有更快,但它确实让我证明是对标量函数的调用导致速度变慢。
那看起来像:
Insert into @cte(recs, setId, c1, c2)
select count(*) over (partition by c.c1) recs, c.setId, c.c1, c.c2
From tbl c
Where c.setId=@setId and c.endDt is null;
Update c
Set nId =schma.scal(c.c1, c.c2, c.c3)
From @cte c
Run Code Online (Sandbox Code Playgroud)
正如我所说,@cte 有 4 到 15 行(我最常用的 setId 中有 11 行)。在此更新之后,然后在此更新之前先返回,这就是我得出结论,这是导致问题的标量函数的方式。基本上是几十毫秒,一秒多,再过几毫秒就完成了。
来自 SSMS 的查询计划没有用,我正在考虑是否可以使用 DMV 中的计划获得更多详细信息。
知道 TVF 可用于替换标量,并且这可以提高性能,我试了一下,它奏效了(在原始 TVF 中调用时,整个查询的时间在 ~100 毫秒范围内)。
我仍然在挠头,为什么从 TVF 内部呼叫比从外部呼叫需要更长的时间。
在 TVF 外部,我使用标量获得的时间与在原始 TFV 中使用新 TFV 的时间相当。
当作为查询的一部分调用时,标量函数被称为每行一次。
考虑以下示例。
为我们的测试创建一个新的空白数据库:
USE master;
IF EXISTS (SELECT 1 FROM sys.databases d WHERE d.name = 'mv')
BEGIN
ALTER DATABASE mv SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE mv;
END
GO
CREATE DATABASE mv;
GO
Run Code Online (Sandbox Code Playgroud)创建一个表、一个多语句函数和一个表值函数:
USE mv;
GO
CREATE TABLE dbo.t
(
t_id int NOT NULL
CONSTRAINT PK_t
PRIMARY KEY CLUSTERED
);
GO
CREATE FUNCTION dbo.t_func
(
@t_id int
)
RETURNS bit
WITH SCHEMABINDING
AS
BEGIN
DECLARE @r bit;
IF EXISTS (SELECT 1 FROM dbo.t WHERE t.t_id = @t_id)
SET @r = 1
ELSE
SET @r = 0;
RETURN @r;
END
GO
CREATE FUNCTION dbo.t_tvf
(
@min_t_id int
, @max_t_id int
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
SELECT t_id = t.t_id
, e = dbo.t_func(dbo.t.t_id)
FROM dbo.t
WHERE t.t_id >= @min_t_id
AND t.t_id <= @max_t_id
);
GO
Run Code Online (Sandbox Code Playgroud)在表中插入一些示例数据:
INSERT INTO dbo.t (t_id)
SELECT ROW_NUMBER() OVER (ORDER BY c.id, c.colid)
FROM sys.syscolumns c;
GO
Run Code Online (Sandbox Code Playgroud)创建一个表来存储函数执行统计信息,并用一个显示多语句函数的执行计数的起始行填充它,t_func
:
CREATE TABLE dbo.function_stats
(
run_num int NOT NULL
, object_name sysname NOT NULL
, execution_count int NULL
, CONSTRAINT PK_function_stats
PRIMARY KEY CLUSTERED (run_num, object_name)
);
GO
INSERT INTO dbo.function_stats (run_num, object_name, execution_count)
SELECT 1
, o.name
, COALESCE(fs.execution_count, 0)
FROM sys.objects o
LEFT JOIN sys.dm_exec_function_stats fs ON fs.object_id = o.object_id
WHERE o.name = 't_func';
GO
Run Code Online (Sandbox Code Playgroud)对 TVF 运行查询:
SELECT t.*
FROM dbo.t_tvf(1, 2) t;
GO
Run Code Online (Sandbox Code Playgroud)现在捕获执行统计信息:
INSERT INTO dbo.function_stats (run_num, object_name, execution_count)
SELECT 2
, o.name
, COALESCE(fs.execution_count, 0)
FROM sys.objects o
LEFT JOIN sys.dm_exec_function_stats fs ON fs.object_id = o.object_id
WHERE o.name = 't_func';
Run Code Online (Sandbox Code Playgroud)函数统计结果:
SELECT *
FROM dbo.function_stats fs
ORDER BY fs.run_num
, fs.object_name;
Run Code Online (Sandbox Code Playgroud)???????????????????????????????????????????????? ? 运行数?对象名称?执行计数? ???????????????????????????????????????????????? ? 1 ? t_func ? 0 ? ? 2 ? t_func ? 2 ? ????????????????????????????????????????????????
如您所见,多语句函数执行了两次,对于 TVF 访问的源表,每行一次。
我预计辑阵语句功能正在被TVF叫很多很多次,给人的印象是它运行缓慢,而实际上它只是被称为很多次。
归档时间: |
|
查看次数: |
1219 次 |
最近记录: |