【发布时间】:2019-10-15 22:31:58
【问题描述】:
14 年来,我一直信奉“永远不要在超过一两行受到影响的行上使用 UDF”。
我正准备通过一个非常基本的示例与我的团队分享 UDF 有多糟糕:
/*
CREATE FUNCTION dbo.Test (@Numerator Float, @Denominator Float)
RETURNS Float AS
BEGIN
DECLARE @Return Float;
SET @Return = @Numerator / NullIf(@Denominator,0);
RETURN @Return
END
GO
*/
----------------------------------------------------------------------
SELECT rn / NullIf(3.00,0)
FROM
(
SELECT TOP 1000000 rn = Convert(Float,ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) tbl;
----------------------------------------------------------------------
SELECT dbo.Test (rn,3.00)
FROM
(
SELECT TOP 1000000 rn = Convert(Float,ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) tbl;
----------------------------------------------------------------------
--DROP FUNCTION dbo.Test;
GO
但是,当我准备做 show-n-tell 时,运行第一个块和第二个块所花费的时间几乎完全相同,大约 8 秒。我记得运行过一个非常相似的实验大约 6 年前,并注意到执行时间呈指数级差异。
UDF 现在可以安全使用了吗?有什么变化?
【问题讨论】:
-
UDF 并不邪恶。像所有代码一样,它们可能写得不好或以不太理想的方式编写。例如,当您的 UDF 可以简化为一个时,它有多个语句 >>> RETURN @Numerator/NullIf(@Denominator,0)
-
像所有工具一样,它们各有利弊。但就像所有 SQL 一样,性能完全取决于情境。在
where子句中避免它们可能是一个有效的规则,但是您是否想在多个位置重复您的 SQL 并冒忘记更新其中一个的风险。在许多情况下,可以将 UDF 替换为 ITVF,它没有典型的 UDF 不良性能,因为它像视图一样插入内联。 -
我知道只做一个直接的“返回”的单一声明,因为这是我的第一个方法。当它快速返回时,我夸大了它以模仿大多数其他最基本的 UDF。它仍然很快返回,因此,我决定发布更复杂的版本,以进一步证明简单 UDF 的性能已显着提高(或者,......也许我的记忆力真的很差)。
-
我确信 SQL Server 的每个部分在 14 年后都会表现得更好:)
-
@AlanBurstein 我的主张成立。每个工具在正确使用时都有价值。首先,我怀疑大多数理性的人永远不会使用 UDF 作为挑战,如果他们这样做了,他们很快就会意识到这是一个愚蠢的想法。其次,性能很重要,但不是一切。还应考虑数据完整性、可管理性、可用性……。例如,UDF 可以通过减少接触点来提高一致性。一位客户添加了风险评级。他们花费了数千个工时来识别、调整和验证无数的遗留报告和流程。我有一个接触点。
标签: sql-server tsql user-defined-functions sql-execution-plan