SQL SERVER中用戶定義標量函數(scalar user defined function)的性能問題

来源:http://www.cnblogs.com/kerrycode/archive/2016/01/25/5158100.html
-Advertisement-
Play Games

用戶定義函數(UDF)分類 SQL SERVER中的用戶定義函數(User Defined Functions 簡稱UDF)分為標量函數(Scalar-Valued Function)和表值函數(Table-Valued Function)。其中表值函數又分為Inline table-valued ...


用戶定義函數(UDF)分類 

     SQL SERVER中的用戶定義函數(User Defined Functions 簡稱UDF)分為標量函數(Scalar-Valued Function)和表值函數(Table-Valued Function)。其中表值函數又分為Inline table-valued functions和Multistatement table-valued functions。

    用戶定義函數(UDF)在 SQL Server 中發揮重要的作用。用戶定義函數可以用於執行複雜的邏輯,可以接受參數並返回數據。很多時候我們需要寫複雜的邏輯,不能使用單個查詢編寫。在這種情況下,用戶定義函數(UDF)發揮了重要的作用。關於用戶定義函數的優點,可以參考官方文檔“用戶定義函數。如下所示:

用戶定義函數的優點

在 SQL Server 中使用用戶定義函數有以下優點:

  • 允許模塊化程式設計。

    只需創建一次函數並將其存儲在資料庫中,以後便可以在程式中調用任意次。用戶定義函數可以獨立於程式源代碼進行修改。

  • 執行速度更快。

    與存儲過程相似,Transact-SQL 用戶定義函數通過緩存計劃併在重覆執行時重用它來降低 Transact-SQL 代碼的編譯開銷。這意味著每次使用用戶定義函數時均無需重新解析和重新優化,從而縮短了執行時間。

    和用於計算任務、字元串操作和業務邏輯的 Transact-SQL 函數相比,CLR 函數具有顯著的性能優勢。Transact-SQL 函數更適用於數據訪問密集型邏輯。

  • 減少網路流量。

    基於某種無法用單一標量的表達式表示的複雜約束來過濾數據的操作,可以表示為函數。然後,此函數便可以在 WHERE 子句中調用,以減少發送至客戶端的數字或行數。

 

 

UDF標量函數(Scalar-Valued Function)影響性能案例

   官方文檔說用戶定義函數(UDF)的執行速度更快,意思是性能非常好,如果你對此深信不疑的話,那麼我只能呵呵了,其實關於用戶定義函數,尤其是標量函數,需要合理使用。有些場景使用不當,則有可能造成性能問題。關於UDF的標量函數會引起性能的問題,下麵我們先看一個我構造的例子吧(AdventureWorks2014),我們需要查詢某個產品有多少訂單(其實也是優化過程中遇到,然後我在此處構造類似這樣的一個案例)

USE AdventureWorks2014;
GO
CREATE FUNCTION Sales.FetchProductOrderNum
(
    @ProuctID  INT
) RETURNS INT
BEGIN
    DECLARE @SaleOrderNum INT;
    SELECT @SaleOrderNum=COUNT(SalesOrderID)  FROM Sales.SalesOrderDetail 
    WHERE ProductID=@ProuctID
    GROUP BY ProductID;
 
    RETURN @SaleOrderNum;
END
GO

clipboard

我們知道Sales.SalesOrderDetail表裡面ProductID=870的訂單數量有4688,而ProductID=897的訂單數量只有2條記錄。那麼執行下麵語句時,性能會有什麼差異呢?

SET STATISTICS TIME ON;
 
SELECT DISTINCT ProductID, Sales.FetchProductOrderNum(ProductID) FROM Sales.SalesOrderDetail
WHERE ProductID=870
 
SELECT DISTINCT ProductID, Sales.FetchProductOrderNum(ProductID) FROM Sales.SalesOrderDetail
WHERE ProductID=897
 
 
SET STATISTICS TIME OFF;

clipboard[1]

為什麼會有這種情況,這是因為SQL語句裡面調用用戶定義標量函數(UDF Scalar Function),都是逐行調用用戶定義函數,這樣需要為每行去提取用戶定義函數的定義,然後去執行這些定義,從而導致了性能問題;更深層次的原因是因為函數採用了過程式的處理方法,而SQL Server查詢數據則是基於數據集合的,這樣在採用過程式的逐行處理時,SQL Server性能就會顯著降低。

那麼我來分析看看這兩個SQL的實際執行計劃:從下麵實際執行計劃,我們可以看到第一個SQL語句執行計劃從Index Seek 到Compute Scalar的數據流變粗了。這個表示第一個SQL語句的Index Seek返回的數據要多。

clipboard[2]

接下來,我們從Compute Scalar(進行一個標量計算並返回計算值)裡面可以看到Actual Number of Rows 的值為4688 和2 。

clipboard[3]

clipboard[4]

而Compute Scalar在此處就是調用標量函數,而標量函數調用總是需要資源開銷和時間的,當調用次數從2次變為4688次時,elapsed time 和CPU time當然會翻了好多倍。實際環境中,用戶定義標量函數的邏輯比上面簡單的案例更複雜,資源開銷更大,所以有時候你會看到性能差距非常懸殊的SQL案例,在工作中我就發現過這樣的情況,有些開發人員對自定義標量函數使用不當影響性能不甚瞭解。甚至是完全不知情。他們對此振振有詞:你看我SQL語句是一樣的,只是參數不同,效率差別這麼大。肯定是資料庫出現了阻塞或性能問題。要麼是伺服器的性能問題,反正我SQL是沒有問題的,你看這一條語句執行才一秒,換個參數就要一分多鐘,這不是你資料庫性能問題,那是什麼? 這樣的一個偽邏輯讓我很無語。(習慣性就讓我和資料庫、伺服器背了一個大黑鍋)。

   回到正題,上面兩個SQL語句的實際執行計劃的Cost比值為81%:19%;Compute Scalar(進行一個標量計算並返回計算值)的Number of Executions都是1次。但是實際的CPU time &elapsed time的比值比這個大了好多。另外第一個SQL的Compute Scalar的代價比值居然只有1%。為什麼會這樣呢?我們是不是很迷惑?

 

clipboard[5]

關於這個大家疑惑的地方,T-SQL User-Defined Functions: the good, the bad, and the ugly (part 1)裡面給了我們一個闡述,截取文章中兩段在此(翻譯如有不當,請參考原文):

 

英文:

      However, you may not be aware that the “Actual Execution Plan” is a dirty rotten liar. Or maybe I should say that the terms “Actual Execution Plan” and “Estimated Execution Plan” are misleading. There is only one execution plan, it gets created when the queries are compiled, and then the queries are executed. The only difference between the “Actual” and the “Estimated” execution plan is that the estimated plan only tells you the estimates for how many rows flow between iterators and how often iterators are executed, and the actual plan adds the actual data for that. But no “actual operator cost” or “actual subtree cost” is added to the corresponding estimated values – and since those costs are the values that the percentages are based on, the percentages displayed in an actual execution plan are still based only on the estimates.

 

翻譯:

    然而,你可能不知道“實際執行計劃”其實是一個骯髒的爛騙子,或者我應該說“實際執行計劃”和“估計執行計劃”誤導你了。當查詢語句編譯後,只有一個實際的執行計劃。“實際執行計劃”與“估計執行計劃”的區別就在於“估計執行計劃”只告訴你估計了有多少行流向迭代和迭代器執行頻率,而實際執行計劃將實際數據應用進來。但是“實際操作成本”或“實際子樹成本”並沒有添加到“實際執行計劃”的估計值裡面, 因為這些代價都是基於百分比的值,在實際執行計劃中顯示的百分比仍然基於只估計數。

 

英文:

    But note that, again, the execution plan is lying. First, it implies that the UDF is invoked only once, which is not the case. Second, look at the cost. You may think that the 0% is the effect of rounding down, since a single execution of the function costs so little in relation to the cost of accessing and aggregating 100,000 rows. But if you check the properties of the iterators of the plan for the function, you’ll see that all operator and subtree costs are actually estimated to be exactly 0. This lie is maybe the worst of all – because it’s not just the plan lying to us, it is SQL Server lying to itself. This cost estimate of 0 is actually used by the query optimizer, so all plans it produces are based on the assumption that executing the function is free. As a result, the optimizer will not even consider optimizations it might use if it knew how costly calling a scalar UDF actually is.

 

翻譯

但是需要再次註意,執行計劃在欺騙你,首先,它意味著只調用了UDF一次,其實不是這樣。其次,從成本(Cost)來看,你可能會認為0%是向下舍入影響,因為單次執行函數的開銷如此之小,以至於執行100,000次的成本也很小。但如果你檢查執行計劃的功能迭代器的屬性,你會發現所有的操作代價和子樹代價實際的估計為0,這是一個最糟糕的謊言。 因為它可能不只是為了欺騙我們,而是SQL SERVER為了欺騙它自己。實際上是查詢優化器認為調用函數的成本為0,因此它生成的所有執行計劃都是基於調用UDF是免費的。其結果是即使調用標量UDF的代價非常昂貴,查詢優化器也不會考慮優化它。

 

如何優化UDF標量函數(Scalar-Valued Function)


如何優化上面SQL語句呢?從原理上來講就是不用用戶定義函數或減少調用次數。 其實我在實際應用中,減少調用次數一般通過下麵方法優化:

1:減少用戶定義標量函數調用次數(子查詢)

SET STATISTICS TIME ON;
 
SELECT ProductID, Sales.FetchProductOrderNum(ProductID)
FROM(
    SELECT DISTINCT ProductID FROM Sales.SalesOrderDetail
WHERE ProductID=870) T
 
SET STATISTICS TIME OFF;

clipboard[6]

 

2:減少用戶定義標量函數調用次數(臨時表)

SET STATISTICS TIME ON;
 
 
SELECT DISTINCT ProductID INTO #SalesOrderDetail FROM Sales.SalesOrderDetail
WHERE ProductID=870;
 
SELECT ProductID, Sales.FetchProductOrderNum(ProductID)
FROM #SalesOrderDetail
 
SET STATISTICS TIME OFF;

 

為什麼要用臨時表呢?不是子查詢就可以解決問題嗎?問題是實際應用當中,有些邏輯複雜的地方需要藉助臨時表解決,有時候子查詢反而不是一個好的解決方法。

clipboard[7]

另外,我們來看看Performance Considerations of User-Defined Functions in SQL Server 2012這篇文章中,測試UDF的性能案例,本想單獨翻譯這篇文章,不過結合這篇文章,在此實驗驗證也是個不錯的選擇。下麵案例全部來自這篇博客。我們先準備測試環境:

CREATE FUNCTION dbo.Triple(@Input INT) 
       RETURNS INT 
AS 
BEGIN; 
  DECLARE @Result INT; 
  SET @Result = @Input * 3; 
  RETURN @Result; 
END; 
GO 
 
CREATE TABLE dbo.LargeTable 
  (KeyVal INT NOT NULL PRIMARY KEY, 
   DataVal INT NOT NULL CHECK (DataVal BETWEEN 1 AND 10) 
  );
 
WITH Digits 
AS (SELECT d FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS d(d)) 
INSERT INTO dbo.LargeTable (KeyVal, DataVal) 
SELECT 1000000 * sm.d 
     + 100000 * ht.d + 10000 * tt.d + 1000 * st.d 
     + 100 * h.d + 10 * t.d + s.d + 1, 
       10 * RAND(CHECKSUM(NEWID())) + 1 
FROM   Digits AS s,  Digits AS t,  Digits AS h, 
       Digits AS st, Digits AS tt, Digits AS ht, 
       Digits AS sm;
GO
 
CREATE INDEX NCL_LargeTable_DataVal ON dbo.LargeTable (DataVal);
GO
 
SET STATISTICS TIME ON; 
 
SELECT MAX(dbo.Triple(DataVal)) AS MaxTriple 
FROM dbo.LargeTable AS d; 
 
SELECT MAX(3 * DataVal) AS MaxTriple 
FROM dbo.LargeTable AS d; 
 
SET STATISTICS TIME OFF;

 

如上所示,用戶定義的標量函數dbo.Triple,測試用的的一個表dbo.LargeTable ,以及構造了1000000行數據。從下麵我們可以看到用戶定義標量函數性能確實很糟糕。

clipboard[8]

下麵測試4中寫法的性能。相信這個簡單的腳本,大家都能看懂,在此不做過多描述、說明:

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫這篇博客的目的是為了跟大家分享本人對多線程的一些淺顯的理解,順道梳理一遍自己掌握的知識,如有不妥之處,歡迎各位大牛指正.首先要理解兩個概念:進程(Process):程式的一次運行,擁有獨立的記憶體地址空間(一個iOS應用只有一個進程).線程(Thread):1.線程是進程的基本執行單元,是操作系統可...
  • listview載入的核心是其adapter,本文針對listview載入的性能優化就是對adpter的優化,總共分四個層次:0、最原始的載入1、利用convertView2、利用ViewHolder3、實現局部刷新[轉載請保留本文地址:http://www.cnblogs.com/goagent/...
  • SELECT Folder_ID ,Folder_Name ,Folder_ParentId FROM dbo. Folder遞歸語句:在此我們以ADO.NET為例,查詢ADO.NET下的子節點:WITHTempFolder(Folder_ID,Folder_Name,Folder_ParentId...
  • 使用資料庫的過程中,由於斷電或其他原因,有可能導致資料庫出現一些小錯誤,比如檢索某些表特別慢,查詢不到符合條件的數據等。出現這些情況的原因,往往是因為資料庫有些損壞,或索引不完整。在ACCESS中,有個修複資料庫的功能可以解決這個問題,在SQL企業管理器,沒有這個功能,要用語句來完成,下麵就介紹如何...
  • Mysql ERROR 1064 (42000)
  • MyCAT日誌對於瞭解MyCAT的運行信息不可獲取,譬如MyCAT是否採用讀寫分離,對於一個查詢語句,MyCAT是怎樣執行的,每個分片會分發到哪個節點上等等。預設是info級別,通過log4j.xml可將其設置debug級別,這樣就可獲得更多有關MyCAT運行的內部信息。下麵通過對MyCAT的啟動以...
  • 常常需要將資料庫中的數據生成文檔,由於比較喜歡腳本的方式,所以就需要使用spool的時候進行格式設置,以下簡單整理了一下oracle中進行格式設置的一些東西,一共十八條,其實常用的也就那麼幾個,稍後會附上自己寫的簡單的shell操作的腳本,希望能供同樣有需要的共同交流,也作為自己的備份。set命令的...
  • MyCAT預設字元集是UTF8下麵通過查看日誌來驗證不同的MySQL客戶端字元集和伺服器字元集對於MyCAT的影響。日誌中與字元集有關的主要有三部分:1. 初始化MyCAT連接池2. 心跳檢測3. 在執行SQL語句時的連接同步。因為MyCAT實現的是三節點的讀寫分離和自動切換,以下修改的均是loca...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...