用户定义函数和存储过程之间的性能差异

Posted

技术标签:

【中文标题】用户定义函数和存储过程之间的性能差异【英文标题】:performance difference between User Defined Function and Stored Procedures 【发布时间】:2009-12-21 21:32:11 【问题描述】:

如果语句返回对数据库进行简单选择的行,那么使用函数和过程实现它之间是否存在性能差异? 我知道最好使用函数,但它真的更快吗?

【问题讨论】:

【参考方案1】:

在函数内部运行的查询和在过程内部运行的查询在速度上没有区别。

存储过程在聚合结果时存在问题,它们不能与其他存储过程组合。 onyl 解决方案非常麻烦,因为它涉及将过程输出捕获到带有INSERT ... EXEC ... 的表中,然后使用结果表。

函数具有高度可组合性的优点,因为表值函数可以放置在需要表表达式的任何位置(FROM、JOIN、APPLY、IN 等)。但是函数在函数中允许什么和不允许什么方面有一些非常严格的限制,这正是因为它们可以出现在查询中的任何位置。

真的是苹果对橘子。决定不是由性能驱动,而是由需求驱动。作为一般规则,任何返回数据集的东西都应该是视图或表值函数。任何操纵数据的东西都必须是一个过程。

【讨论】:

为什么?那为什么在您的应用程序中,当我们使用函数和过程时,执行速度不同????怎么样?【参考方案2】:

并非所有 UDF 都对性能不利。

普遍存在一种误解,即 UDF 会对性能产生不利影响。笼统地说,这根本不是真的。事实上,内联表值 UDF 实际上是宏——优化器非常有能力重写涉及它们的查询以及优化它们。但是,标量 UDF 通常非常慢。我将提供一个简短的示例。

先决条件

这是创建和填充表格的脚本:

CREATE TABLE States(Code CHAR(2), [Name] VARCHAR(40), CONSTRAINT PK_States PRIMARY KEY(Code))
GO
INSERT States(Code, [Name]) VALUES('IL', 'Illinois')
INSERT States(Code, [Name]) VALUES('WI', 'Wisconsin')
INSERT States(Code, [Name]) VALUES('IA', 'Iowa')
INSERT States(Code, [Name]) VALUES('IN', 'Indiana')
INSERT States(Code, [Name]) VALUES('MI', 'Michigan')
GO
CREATE TABLE Observations(ID INT NOT NULL, StateCode CHAR(2), CONSTRAINT PK_Observations PRIMARY KEY(ID))
GO
SET NOCOUNT ON
DECLARE @i INT
SET @i=0
WHILE @i<100000 BEGIN
  SET @i = @i + 1
  INSERT Observations(ID, StateCode)
  SELECT @i, CASE WHEN @i % 5 = 0 THEN 'IL'
    WHEN @i % 5 = 1 THEN 'IA'
    WHEN @i % 5 = 2 THEN 'WI'
    WHEN @i % 5 = 3 THEN 'IA'
    WHEN @i % 5 = 4 THEN 'MI'
    END
END
GO

当涉及 UDF 的查询被重写为外连接时。

考虑以下查询:

SELECT o.ID, s.[name] AS StateName
  INTO dbo.ObservationsWithStateNames_Join
  FROM dbo.Observations o LEFT OUTER JOIN dbo.States s ON o.StateCode = s.Code

/*
SQL Server parse and compile time:
   CPU time = 0 ms, elapsed time = 1 ms.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Observations'. Scan count 1, logical reads 188, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'States'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 187 ms,  elapsed time = 188 ms.
*/

并将其与涉及值 UDF 的内联表的查询进行比较:

CREATE FUNCTION dbo.GetStateName_Inline(@StateCode CHAR(2))
RETURNS TABLE
AS
RETURN(SELECT [Name] FROM dbo.States WHERE Code = @StateCode);
GO
SELECT ID, (SELECT [name] FROM dbo.GetStateName_Inline(StateCode)) AS StateName
  INTO dbo.ObservationsWithStateNames_Inline
  FROM dbo.Observations

它的执行计划和执行成本都是相同的——优化器已将其重写为外连接。不要低估优化器的威力!

涉及标量 UDF 的查询要慢得多。

这是一个标量 UDF:

CREATE FUNCTION dbo.GetStateName(@StateCode CHAR(2))
RETURNS VARCHAR(40)
AS
BEGIN
  DECLARE @ret VARCHAR(40)
  SET @ret = (SELECT [Name] FROM dbo.States WHERE Code = @StateCode)
  RETURN @ret
END
GO

显然,使用此 UDF 的查询提供了相同的结果,但它具有不同的执行计划,而且速度非常慢:

/*
SQL Server parse and compile time:
   CPU time = 0 ms, elapsed time = 3 ms.
Table 'Worktable'. Scan count 1, logical reads 202930, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Observations'. Scan count 1, logical reads 188, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 11890 ms,  elapsed time = 38585 ms.
*/

如您所见,优化器可以重写和优化涉及内联表值 UDF 的查询。另一方面,涉及标量 UDF 的查询不会被优化器重写——最后一个查询的执行包括每行一个函数调用,这非常慢。

Not all UDFs are bad for performance.

【讨论】:

【参考方案3】:

一旦 SQL 看到 BEGIN 或 END,系统就无法将内容简化出来。

所以真正的区别只是归结为一个事实,一个函数的结果可以用于外部查询,用于连接,忽略某些列等等。

您最好的选择实际上是使用视图或内联表值函数,以便 SQL 可以简化它并只执行您感兴趣的部分。查找我关于“BEGIN 的危险和END”在我的博客上了解更多信息。

【讨论】:

msmvps.com/blogs/robfarley/archive/2009/12/05/… 是我的意思。【参考方案4】:

我认为您不应该关心速度,而是关心如何使用此功能。 UDF 可以出现在 select 语句中的其他地方,甚至可以用作“表”来加入等。您不能从存储过程中“选择”或加入其中。

但是,每行都调用 UDF,所以我会小心你在哪里使用它。这让我遇到了真正的麻烦。太多了,我永远不会忘记。

【讨论】:

UDF 只有在被视为程序性时才是坏的。您应该考虑一个内联表值函数,它将像视图一样简化。这是可以真正提高性能的地方。 您确定每行都调用所有 UDF 吗?这仅适用于标量 UDF。内联的错误。 @Alex - 是的 +1 - 澄清罐【参考方案5】:

简单的 SELECT 语句将受您正在查询的表上的任何索引的影响最大。

优化器位于您选择的数据库引擎的核心,负责就传入的查询如何运行做出重要决策。

在编写查询时,值得花时间学习索引、优化器、主键等。挑选一些数据库引擎; SQL Server 与 mysql 不同,而 Oracle 与它们两者都不同。还有更多,每一种在某些方面都不同。

存储过程可以很快,非常快,因为它们是预编译的。优化器不必每次都制定执行计划。存储过程将以表格形式返回结果。

函数可以是标量(返回单个结果)或返回表格数据。

很可能编写低效的函数和存储过程。重要的是要问自己是否需要该功能以及如何维护它。

如果您还没有一本 Joe Celko 的书,那么现在可能是投资的时候了。

【讨论】:

【参考方案6】:

我第一次尝试使用内联表值函数 (TVF) 时,实际上花费了 66% 到 76%(1.147 到 1.2 对 0.683 秒)更长(与存储过程相比) (SP))!?!这是 100 次迭代的平均值,每次迭代 89 行。我的 SP 只是在做标准的set nocount on,然后是一个复杂的(但仍然是单一的)select 语句(有 5 个inner join 和 2 个outer join(其中一个inner join 有一个on 带有嵌入 select 的表达式(它本身有一个 where 表达式(带有嵌入的 select + inner join))和一个 group byorder by,带有 5 列和一个 count )。调用者是一个insert into 一个临时表(有一个identity 列但没有键或索引) - 语句。即使没有 SP 执行的order by,内联 TVF 的时间也延长了 66%。当我将其重新添加(到调用内联 TVF 的 select 中,因为内联 TVF 中不能有 order by)时,花费的时间甚至更长(76%)!?!

【讨论】:

以上是关于用户定义函数和存储过程之间的性能差异的主要内容,如果未能解决你的问题,请参考以下文章

视图和存储过程之间是不是存在性能差异

存储过程 - 函数性能差异

存储过程和函数之间有几个区别

存储过程和用户​​定义函数的区别

Sqlserver中存储过程,触发器,自定义函数

从 MS SQL SERVER 和 MS ACCESS 调用存储过程的结果之间的差异