2010-09-15 50 views
4

我正在考慮在一些查詢中使用某些用戶定義的函數調用,而不是使用一堆內聯的case語句。內聯語句可能會表現更好,但這些函數使得查看和維護起來更容易。用戶自定義函數最佳實踐

我只想了解UDF的典型最佳實踐是什麼?我意識到在標準(Where Clause)中使用它們會對性能產生重大影響。

特別是在那些情況下,你可以有大量的case語句塊或甚至嵌套的case語句。

感謝,

小號

+0

最佳做法是不使用功能出於安全原因。 – Woot4Moo 2010-09-15 17:46:36

回答

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複製

+0

很好的答案,pasib) – 2011-07-01 09:32:40

+0

你不應該從標量函數中的表中查詢。期。 SQL Server根本不優化它們。它們甚至被排除在查詢計劃之外。你實際上是在SQL中編寫一個N + 1查詢。 N + 1查詢是惡魔,並且永遠不會執行。 – BradLaney 2015-12-04 20:17:07

6

不要使用功能僅僅是爲了美觀的緣故。這可以通過使用一致的代碼格式來處理。

在您描述的情況下,您創建了一個外部依賴項 - 該函數必須存在並且對用戶可見,以便運行該查詢。直到SQL Server支持與Oracle軟件包相同的東西(程序集不是原生SQL)... ...

還有一種陷入相信SQL函數像過程/ OO編程中的方法/函數一樣執行的陷阱的風險,它們不要這樣查詢可以執行與功能,而不是沒有。

+0

感謝OMG我很欣賞這些反饋。 「最近從Oracle切換到」 – scarpacci 2010-09-15 17:39:55

1

我傾向於避免大多數函數,因爲儘管它們比在線case語句更漂亮,但它們也傾向於使查詢計劃不太準確。如果你在函數中隱藏了很多複雜性,那麼複雜性也往往隱藏在查詢計劃中,所以如果你有問題並且稍後需要調整查詢,你通常會修復顯示成本過高的東西但與UDF相比,其成本實際上是微不足道的。

+0

這隻適用於標量UDF,並且對於內聯UDF完全錯誤。 – 2010-09-15 19:52:31