我正在考慮在一些查詢中使用某些用戶定義的函數調用,而不是使用一堆內聯的case語句。內聯語句可能會表現更好,但這些函數使得查看和維護起來更容易。用戶自定義函數最佳實踐
我只想了解UDF的典型最佳實踐是什麼?我意識到在標準(Where Clause)中使用它們會對性能產生重大影響。
特別是在那些情況下,你可以有大量的case語句塊或甚至嵌套的case語句。
感謝,
小號
我正在考慮在一些查詢中使用某些用戶定義的函數調用,而不是使用一堆內聯的case語句。內聯語句可能會表現更好,但這些函數使得查看和維護起來更容易。用戶自定義函數最佳實踐
我只想了解UDF的典型最佳實踐是什麼?我意識到在標準(Where Clause)中使用它們會對性能產生重大影響。
特別是在那些情況下,你可以有大量的case語句塊或甚至嵌套的case語句。
感謝,
小號
我都會這樣回答:
有一種普遍的誤解是,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複製
很好的答案,pasib) – 2011-07-01 09:32:40
你不應該從標量函數中的表中查詢。期。 SQL Server根本不優化它們。它們甚至被排除在查詢計劃之外。你實際上是在SQL中編寫一個N + 1查詢。 N + 1查詢是惡魔,並且永遠不會執行。 – BradLaney 2015-12-04 20:17:07
不要使用功能僅僅是爲了美觀的緣故。這可以通過使用一致的代碼格式來處理。
在您描述的情況下,您創建了一個外部依賴項 - 該函數必須存在並且對用戶可見,以便運行該查詢。直到SQL Server支持與Oracle軟件包相同的東西(程序集不是原生SQL)... ...
還有一種陷入相信SQL函數像過程/ OO編程中的方法/函數一樣執行的陷阱的風險,它們不要這樣查詢可以執行差與功能,而不是沒有。
感謝OMG我很欣賞這些反饋。 「最近從Oracle切換到」 – scarpacci 2010-09-15 17:39:55
我傾向於避免大多數函數,因爲儘管它們比在線case語句更漂亮,但它們也傾向於使查詢計劃不太準確。如果你在函數中隱藏了很多複雜性,那麼複雜性也往往隱藏在查詢計劃中,所以如果你有問題並且稍後需要調整查詢,你通常會修復顯示成本過高的東西但與UDF相比,其成本實際上是微不足道的。
這隻適用於標量UDF,並且對於內聯UDF完全錯誤。 – 2010-09-15 19:52:31
最佳做法是不使用功能出於安全原因。 – Woot4Moo 2010-09-15 17:46:36