在SQL SERVER中,自定義函數可以划分成:
1.內聯表值函數
2.多語句表值函數
3.標量值函數
上述三類自定義函數如果使用不當,就會造成性能問題,本片重點關注“多語句表值函數”。
在多語句表值函數在每次調用時都需要使用到一個臨時表來存放返回值,因此如果頻繁調用該函數,會影響tempdb的性能。
測試代碼:
--========================================================================= --創建測試表 GO SELECT * INTO TB001 FROM sys.all_objects GO SELECT * INTO TB002 FROM sys.all_columns GO --========================================================================= --創建內聯表值函數 CREATE FUNCTION [dbo].[ufn_GetTop2Columns2] ( @object_ID BIGINT ) RETURNS TABLE AS RETURN ( SELECT TOP(2) name AS ColumnName FROM TB002 WHERE OBJECT_ID=@object_ID ) GO --========================================================================= --多語句表值函數 CREATE FUNCTION [dbo].[ufn_GetTop2Columns] ( @object_ID BIGINT ) RETURNS @result TABLE ( ColumnName NVARCHAR(200) ) AS BEGIN INSERT INTO @result SELECT TOP(2) name AS ColumnName FROM TB002 WHERE OBJECT_ID=@object_ID RETURN END GO SET STATISTICS IO ON SET STATISTICS TIME ON GO --========================================================================= --不使用表值函數 SELECT * FROM TB001 AS T1 CROSS APPLY (SELECT TOP(2) * FROM TB002 AS T2 WHERE T1.Object_id=T2.Object_id ) AS T3 --運行結果 --表 'Worktable'。掃描計數 1989,邏輯讀取 15095 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 'TB002'。掃描計數 1,邏輯讀取 54 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 'TB001'。掃描計數 1,邏輯讀取 34 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --SQL Server 執行時間: --CPU 時間 = 94 毫秒,占用時間 = 543 毫秒。 --========================================================================= --使用多語句表值函數 SELECT * FROM TB001 AS T1 CROSS APPLY dbo.ufn_GetTop2Columns(T1.Object_id) AS T3 --運行結果 --表 '#756D6ECB'。掃描計數 1989,邏輯讀取 1989 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 'TB001'。掃描計數 1,邏輯讀取 34 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --SQL Server 執行時間: --CPU 時間 = 7129 毫秒,占用時間 = 7262 毫秒。 --========================================================================= --內聯表值函數 SELECT * FROM TB001 AS T1 CROSS APPLY dbo.ufn_GetTop2Columns2(T1.Object_id) AS T3 --運行結果 --表 'Worktable'。掃描計數 1989,邏輯讀取 14736 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 'TB002'。掃描計數 1,邏輯讀取 54 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 'TB001'。掃描計數 1,邏輯讀取 34 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --SQL Server 執行時間: --CPU 時間 = 62 毫秒,占用時間 = 186 毫秒。
多次運行發現,使用內聯表值函數執行速度(186ms)快於未使用自定義函數的語句(543ms),而多語句表值函數的執行速度最慢(7262ms).
優化建議:
1. 將多語句表值函數改寫成內聯表值函數或不使用自定義函數的語句。
2. 將CROSS APPLY改寫成INNER JOIN ,以減少多語句表值函數的調用次數
如將上面的語句改成:
CREATE FUNCTION [dbo].[ufn_GetTop2Columns3] ( ) RETURNS @result TABLE ( ColumnName NVARCHAR(200), Object_id BIGINT ) AS BEGIN INSERT INTO @result(ColumnName,Object_id) SELECT ColumnName,Object_id FROM (SELECT ROW_NUMBER()OVER(PARTITION BY T2.Object_id ORDER BY T2.Object_id) AS RID, T2.name AS ColumnName, T2.Object_id FROM TB002 AS T2 ) AS T3 WHERE RID<3 RETURN END GO --=================================================================== --將CROSS APPLY改寫成INNER JOIN, SELECT * FROM TB001 AS T1 INNER JOIN [dbo].[ufn_GetTop2Columns3]() AS T3 ON T1.Object_id=T3.Object_id --表 'Worktable'。掃描計數 0,邏輯讀取 0 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 'TB001'。掃描計數 1,邏輯讀取 34 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --表 '#28ED12D1'。掃描計數 1,邏輯讀取 5 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。 --SQL Server 執行時間: --CPU 時間 = 47 毫秒,占用時間 = 383 毫秒。
當然,不是所有的多語句表值函數都可以被改寫,在優化時測試各種優化方案,尋找到一種最適合業務場景的方法。