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

时间:2009-12-21 21:32:12

标签: sql-server performance stored-procedures function

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

6 个答案:

答案 0 :(得分:18)

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

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

函数具有高度可组合的优点,因为表值函数可以放在预期表表达式的任何位置(FROM,JOIN,APPLY,IN等)。但是函数在函数允许的内容和非函数中有一些非常严重的限制,正是因为它们可以出现在查询中的任何位置。

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

答案 1 :(得分:14)

并非所有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.

答案 2 :(得分:1)

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

但是,每次都会调用UDF,因此在使用它时要小心。这让我陷入了困境。我永远不会忘记这么多。

答案 3 :(得分:1)

只要SQL看到BEGIN或END,系统就无法简化内容。

所以真正的区别只是因为函数的结果可以用在外部查询中,用于连接,忽略某些列等等。

你最好的选择是使用视图或内联表值函数,这样SQL就可以简化它并只做你感兴趣的部分。查看我的帖子“BEGIN的危险和结束“在我的博客上获取更多信息。

答案 4 :(得分:0)

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

Optimiser位于您选择的数据库引擎的核心,负责制定有关如何传入查询的重要决策。

在编写查询时,值得花时间学习索引,优化器,主键等。选几个数据库引擎; SQL Server与mySQL不同,Oracle与它们不同。还有更多,每种都有所不同。

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

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

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

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

答案 5 :(得分:0)

我第一次尝试使用内联表值函数(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%的时间。当我将其添加回select调用内联TVF,因为你在内联TVF中没有order by时,它花了更长时间(76%)!?!