用户定义函数最佳实践

时间:2010-09-15 17:23:36

标签: sql sql-server tsql sql-server-2008

我正在考虑在我的一些查询中使用一些用户定义的函数调用,而不是使用一堆内联case语句。内联语句可能会表现得更好,但这些功能使得查看和维护更加容易。

我只是想了解UDF的典型最佳做法是什么?我意识到在标准(Where子句)中使用它们会对性能产生一些重大影响。

特别是在你的case块中有很多when语句甚至嵌套case语句的情况下。

谢谢,

取值

3 个答案:

答案 0 :(得分:9)

我的回答:

有一种流行的误解,认为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的查询不会被优化器重写 - 最后一个查询的执行包括每行一个函数调用,这非常慢。 复制自here

答案 1 :(得分:6)

不要仅仅为了美观而使用功能。这可以通过使用一致的代码格式来处理。

在您描述的情况下,您创建了一个外部依赖项 - 该函数必须存在,并且对用户可见,以便运行查询。直到SQL Server支持与Oracle包相同的内容(程序集不是本机SQL)...

还存在陷入陷阱的风险,即认为SQL函数在过程/ OO编程中像方法/函数一样执行,但他们不这样做,因此查询可以使用函数执行更糟而不是不

答案 2 :(得分:1)

我倾向于避免使用大多数函数,因为虽然它们比内联案例语句更漂亮,但它们也倾向于使查询计划不太准确。如果你在函数中隐藏了很多复杂性,那么复杂性也往往会隐藏在查询计划之外,所以如果你遇到问题并且需要稍后调整查询,你通常会修复那些显示成本高的东西。但与UDF相比,实际上是微不足道的成本。