優化案例--多語句表值函數的影響


在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 毫秒。

 

當然,不是所有的多語句表值函數都可以被改寫,在優化時測試各種優化方案,尋找到一種最適合業務場景的方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM