优化案例--多语句表值函数的影响

在SQL SERVER中,自定义函数可以划分成:

1.内联表值函数

2.多语句表值函数

3.标量值函数

上述三类自定义函数如果使用不当,就会造成性能问题,本片重点关注“多语句表值函数”。

 

在多语句表值函数在每次调用时都需要使用到一个临时表来存放返回值,因此如果频繁调用该函数,会影响tempdb的性能。

测试代码:

复制代码
--=========================================================================
--创建测试表
GO
SELECT * INTO TB001 FROM sys.all_objects
GO
SELECT * INTO TB002 FROM sys.all_columns
GO
--=========================================================================
--创建内联表值函数
CREATE FUNCTION [dbo].[ufn_GetTop2Columns2]
(    
    @object_ID BIGINT
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT TOP(2) name AS ColumnName 
    FROM TB002
    WHERE OBJECT_ID=@object_ID
)
GO
--=========================================================================
--多语句表值函数
CREATE FUNCTION [dbo].[ufn_GetTop2Columns]
(
    @object_ID BIGINT
)
RETURNS @result TABLE 
(
    ColumnName NVARCHAR(200)
)
AS
BEGIN
    INSERT INTO @result
    SELECT TOP(2) name AS ColumnName 
    FROM TB002
    WHERE OBJECT_ID=@object_ID
    RETURN 
END
GO
SET STATISTICS IO ON
SET STATISTICS TIME ON
GO
--=========================================================================
--不使用表值函数
SELECT *
FROM TB001 AS T1
CROSS APPLY (SELECT TOP(2) * 
FROM TB002 AS T2 
WHERE T1.Object_id=T2.Object_id ) AS T3
--运行结果
--表 'Worktable'。扫描计数 1989,逻辑读取 15095 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 'TB002'。扫描计数 1,逻辑读取 54 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 'TB001'。扫描计数 1,逻辑读取 34 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

--SQL Server 执行时间:
--CPU 时间 = 94 毫秒,占用时间 = 543 毫秒。


--=========================================================================
--使用多语句表值函数
SELECT *
FROM TB001 AS T1
CROSS APPLY dbo.ufn_GetTop2Columns(T1.Object_id) AS T3
--运行结果
--表 '#756D6ECB'。扫描计数 1989,逻辑读取 1989 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 'TB001'。扫描计数 1,逻辑读取 34 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--SQL Server 执行时间:
--CPU 时间 = 7129 毫秒,占用时间 = 7262 毫秒。
--=========================================================================
--内联表值函数
SELECT *
FROM TB001 AS T1
CROSS APPLY dbo.ufn_GetTop2Columns2(T1.Object_id) AS T3
--运行结果
--表 'Worktable'。扫描计数 1989,逻辑读取 14736 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 'TB002'。扫描计数 1,逻辑读取 54 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 'TB001'。扫描计数 1,逻辑读取 34 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--SQL Server 执行时间:
--CPU 时间 = 62 毫秒,占用时间 = 186 毫秒。
复制代码

多次运行发现,使用内联表值函数执行速度(186ms)快于未使用自定义函数的语句(543ms),而多语句表值函数的执行速度最慢(7262ms).

 

优化建议:

1. 将多语句表值函数改写成内联表值函数或不使用自定义函数的语句。

2. 将CROSS APPLY改写成INNER JOIN ,以减少多语句表值函数的调用次数

如将上面的语句改成:

复制代码
CREATE FUNCTION [dbo].[ufn_GetTop2Columns3]
(
)
RETURNS @result TABLE 
(
    ColumnName NVARCHAR(200),
    Object_id BIGINT
)
AS
BEGIN
    INSERT INTO @result(ColumnName,Object_id)
    SELECT ColumnName,Object_id
    FROM
    (SELECT ROW_NUMBER()OVER(PARTITION BY T2.Object_id ORDER BY T2.Object_id) AS RID,
    T2.name AS ColumnName,
    T2.Object_id 
    FROM TB002 AS T2 
    ) AS T3
    WHERE RID<3

    RETURN 
END
GO
--===================================================================
--将CROSS APPLY改写成INNER JOIN,
SELECT *
FROM TB001 AS T1
INNER JOIN [dbo].[ufn_GetTop2Columns3]() AS T3
ON T1.Object_id=T3.Object_id
--表 'Worktable'。扫描计数 0,逻辑读取 0 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 'TB001'。扫描计数 1,逻辑读取 34 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
--表 '#28ED12D1'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

--SQL Server 执行时间:
--CPU 时间 = 47 毫秒,占用时间 = 383 毫秒。
复制代码

 

当然,不是所有的多语句表值函数都可以被改写,在优化时测试各种优化方案,寻找到一种最适合业务场景的方法。

 

posted on   笑东风  阅读(2748)  评论(1编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

导航

点击右上角即可分享
微信分享提示