Stored Procedure 裏的 WITH RECOMPILE 究竟是幹麻的?

在 SQL Server 建立或修改「存儲過程(stored procedure)」時,可加上 WITH RECOMPILE 選項,但多數文檔或書籍都寫得語焉不詳,或只解釋爲「每次執行此存儲過程時,都要從新編譯」。事實上,是指執行此一存儲過程時,要強制從新產生「執行計劃(execution plan)」,而不要從「緩存(cache)」去取得舊的「執行計劃」。html

SQL Server 在評估與產生「執行計劃」時,很是耗 CPU 資源,所以,如何讓其正確地從 cache 中,重複使用舊的「執行計劃」就很重要;可是,若誤用舊的「執行計劃」,致使 SELECT 查詢的性能大幅降低,則更得不償失。前端

通常的 SQL 查詢,兩次或屢次執行的 SQL 語句中,內容必須徹底符合,才能延用舊的「執行計劃」,包含: 大小寫、換行、空白。以下圖 1,由於兩次執行的 SQL 語句,差了一個「半形空格」,致使產生了兩次「執行計劃」,而沒法重複使用舊的「執行計劃」。sql


圖 1 浪費資源產生了兩次「執行計劃」 數據庫

1 DBCC FREEPROCCACHE
2 
3 SELECT * FROM Customers SELECT * FROM Orders
4 GO
5 SELECT * FROM Customers  SELECT * FROM Orders
6 
7 SELECT cacheobjtype, objtype, usecounts, sql FROM sys.syscacheobjects 
8 WHERE sql NOT LIKE '%cache%' AND sql NOT LIKE '%sys.%'
View Code

如果改用「參數化查詢」,以下 :
SELECT * FROM Customers WHERE CustomerID=@CustomerID
便可避免因參數值不一樣,一直產生新的「執行計劃」,亦可避免 SQL Injection 攻擊。緩存

而存儲過程,相對於通常 SQL 語句,其在性能上的優點,除了已事先編譯外,存儲過程也可提高「執行計劃」的重用性(複用性),避免產生新的「執行計劃」、消耗 CPU 資源。以下圖 2,兩次調用同一個存儲過程時,但傳入不一樣的參數,SQL Server 會重複使用同一個「執行計劃」,如同上述的「參數化查詢」同樣,不會浪費資源產生新的「執行計劃」。ide


圖 2 「執行計劃」被重複使用,避免浪費資源wordpress

 1 CREATE PROC spCust1 @CustID NVARCHAR(5)
 2 AS
 3 SELECT * FROM dbo.Customers 
 4 WHERE CustomerID=@CustID
 5 GO
 6 
 7 EXEC spCust1 'ALFKI'
 8 EXEC spCust1 'BERGS'
 9 
10 --DBCC FREEPROCCACHE
11 SELECT cacheobjtype, objtype, usecounts, sql FROM sys.syscacheobjects 
12 WHERE sql NOT LIKE '%cache%' AND sql NOT LIKE '%sys.%'
View Code

但若存儲過程「數據內容分佈不平均」,例如某個 Table,裏面有個 Int 類型的字段,大量記錄裏所存儲的值依序爲 1~100,但只有某一條記錄存的是 10000。亦即符合過濾條件的記錄有時極多 (「執行計劃」適合用「索引掃描」),但有時符合的只有一兩條 (「執行計劃」適合用「索引查找」)。而將來在調用此存儲過程時,兩種情境都有可能出現,所以咱們但願此一存儲過程,在執行時「不要 cache 執行計劃」,亦即讓此存儲過程在每次執行時,都從新評估、產生最適當的「執行計劃」,此時就可加上 WITH RECOMPILE 選項。或者以下圖 3,丟給前端應用程序去決定,亦即 AP 在調用此存儲過程時,再決定是否加上 WITH RECOMPILE 參數。函數


圖 3性能

1 Exec select_Proc1 @Key1=5       --自動選用高效能的「執行計畫」
2 Exec select_Proc1 @Key1=10000   --從 cache 延用舊的「執行計畫」,因不適用,反而導致效能不佳
3 Exec select_Proc1 @Key1=10000 WITH RECOMPILE --強制從新產生新的、高效能「執行計畫」
View Code

還有其餘進階的選項應用,像是能夠在建立存儲過程時,使用 OPTIMIZE FOR 選項,只針對特定某一個參數值來作 cache,來產生固定一種、平均對性能影響最小的「執行計劃」,又能避免一直重複產生新的「執行計劃」而浪費 CPU 資源。spa


案例分析 - 一樣的語法在存儲過程內跑很慢,單獨跑很快 (胡百敬, 繁體中文) :
http://byronhu.wordpress.com/2010/07/15/with-recompile/

引用該文部份內容 :

朋友問了一個有趣的問題:一樣的語句,在存儲過程內跑很慢,單獨跑很快。

存儲過程會緩存執行計劃 (若未加上 WITH RECOMPILE),通常來講能夠省掉 CPU 耗費。但若兩次執行此存儲過程的期間,所引用的記錄數量差別很大,則第二次執行時沿用舊的執行計劃,性能會變得不好。能夠觀察如下現象:

  1. 觀察執行後的執行計劃,傳回大量記錄倒是用「索引查找」。
  2. 透過 Profiler 觀察存儲過程內的語法,和單獨執行的語法,所耗的 IO/CPU/Duration 的數值。若將某句的語法單獨拿到 Management Studio 執行的性能,遠好於該句語法在存儲過程內執行,就有多是上述緣由。

簡單的解法,是在執行或建立存儲過程時,搭配 WITH RECOMPILE 選項。

...中間略...

存儲過程的執行情境能夠分 80-20 定律,若少數執行情況 AP 本身知道,則 AP 能夠判讀是否要下 with recompile 或是撰寫存儲過程直接搭配 Option(Optimize for (參數定義))

但在一些情況,例如使用者下 Range 查詢,或是「財務滾算」數據,會大量刪除、插入中繼表內的數據,developer 沒法預先評估可能的數據量大小,則在存儲過程建立時,直接搭配 with recompile,可獲得較穩定的執行性能。


結語: 我本身早年寫 AP 時,一直查不到 WITH RECOMPILE 是幹麻的,當時我寫用來「分頁(換頁)」的存儲過程時 (雙 TOP 夾擊、或 ROW_NUMBER 函數),就一概加上 WITH RECOMPILE 選項。如今回想起來,實際上是沒必要加的,由於重複用舊的「執行計劃」便可 (可節省許多數據庫伺服器上的資源),丟入的參數也都差很少 (用戶目前所在頁數、每頁要傳回幾條記錄)。

 1 CREATE PROCEDURE [dbo].[GridView_pager]
 2 @StartRowIndex    int,
 3 @PageSize int,
 4 @tableName nvarchar(50),
 5 @columnName nvarchar(100),
 6 @sqlWhere nvarchar(1000),
 7 @groupBy nvarchar(100),
 8 @orderBy nvarchar(100),
 9 @rowCount int output
10 WITH RECOMPILE
11 AS
View Code

相關文章 :

談一談 SQL Server 中的執行計劃緩存
http://www.cnblogs.com/CareySon/archive/2013/05/04/3058592.html
http://www.cnblogs.com/CareySon/archive/2013/05/04/PlanCacheInSQLServerPart2.html

相關文章
相關標籤/搜索