SQL SERVER 臨時表導致存儲過程重編譯(recompile)的一些探討


    SQLSERVER為了確保返回正確的值,或者處於性能上的顧慮,有意不重用緩存在內存里的執行計划,而重新編譯執行計划的這種行為,被稱為重編譯(recompile)。那么引發存儲過程重編譯的條件有哪一些呢?下面羅列了一些導致重編譯(recompile)的條件:

    - 對查詢所引用的表或視圖進行更改(ALTER TABLE 和 ALTER VIEW)。

    - 對執行計划所使用的任何索引進行更改。

    - 對執行計划所使用的統計信息進行更新,這些更新可能是從語句(如 UPDATE STATISTICS)中顯式生成,也可能是自動生成的。

    - 刪除執行計划所使用的索引。

    - 顯式調用 sp_recompile。

    - 對鍵的大量更改(其他用戶對由查詢引用的表使用 INSERT 或 DELETE 語句所產生的修改)。

    - 對於帶觸發器的表,插入的或刪除的表內的行數顯著增長。

    - 使用 WITH RECOMPILE 選項執行存儲過程。

    - 有些DBCC FREEPROCCACHE;分離、附加數據庫、數據升級也會清除內存里緩存的執行計划

好了,切入到今天我們要關注的問題:臨時表的數據變化導致存儲過程重編譯問題,其實臨時表的數據變化導致存儲過程重編譯實質上是因為臨時表的數據變化,導致了臨時表統計信息的自動更新,從而引起的重編譯。那么觸發臨時表的統計信息的更新的條件或閥值是什么呢?說來也簡單,就是下面一個這個公式(n表示變更前臨時表的數據記錄數,確切的說是上一次采集統計信息時臨時表的記錄數

Temporary table

  1. If n < 6, RT = 6.
  2. If 6 <= n <= 500, RT = 500.
  3. If n > 500, RT = 500 + 0.20 * n.

有個網友說存儲過程中的臨時表數據變更的閥值有問題:他的原話如下

If n < 6, Recompilation threshold = 6.
If 6 <= n <= 500, Recompilation threshold = 500.

上面這兩個區間沒有問題。但是大於500的之后,根本就不是變化大於20%之后再重編譯。看了他提出的問題,其實我也不是特肯定,畢竟沒有實際驗證過。實踐才是檢驗整理的唯一標准,那么我們就開始做實驗吧,首先准備一下測試環境(Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (X64) ).腳本如下所示:

USE MyDBA;
GO
 
IF EXISTS(SELECT 1 FROM sys.sysobjects WHERE id=object_id(N'[dbo].[TEST]') AND OBJECTPROPERTY(id, N'IsTable')=1 )
BEGIN
 DROP TABLE dbo.TEST;
 
    CREATE TABLE TEST
    (
          ID INT IDENTITY(1, 1) ,
          NAME VARCHAR(40)     ,
          CONSTRAINT PK_TEST PRIMARY KEY(ID)
    )
END
GO
 
INSERT INTO TEST VALUES(NEWID())
GO 10000
 
CREATE PROCEDURE Usp_Recompile_TEST(@Index INT)
AS
BEGIN
 CREATE TABLE #T(ID   INT , NAME VARCHAR(40));
 
 INSERT INTO #T SELECT ID, NAME FROM TEST WHERE ID <=@Index;
 
 SELECT  m.* FROM #T m INNER JOIN TEST n ON m.ID = n.ID
END
GO

准備好測試環境后,那么此時我們打開SQL Server工具SQL Server Profiler,選擇“SP:Recompile”和“SP:Complete”事件,然后取消一些選擇列,僅僅選擇一些需要的列,例如 EventClass、TextData等。如下所示

clipboard

開啟Profile跟蹤后,我們打開一個會話窗口,勾選“包括實際的執行計划”,然后再窗口執行下面SQL語句

EXEC dbo.Usp_Recompile_TEST 1;

如下所示,實際的執行計划中,我們看到“估計行數”和“實際行數”是一致的。

clipboard[1]

EXEC dbo.Usp_Recompile_TEST 2;
 
EXEC dbo.Usp_Recompile_TEST 6;

執行上面兩個語句,我們會發現“估計行數”與“實際行數”開始出現偏差,因為數據庫對臨時表#T沒有最新的統計信息,還是上一次收集的統計信息時的數據(1行數據)

clipboard[2]

EXEC dbo.Usp_Recompile_TEST 7;  此時已經觸發了對臨時表統計信息的采集更新(請見后面闡述)。

clipboard[3]

EXEC dbo.Usp_Recompile_TEST 130;
 
EXEC dbo.Usp_Recompile_TEST 500;
 
EXEC dbo.Usp_Recompile_TEST 506;
 
EXEC dbo.Usp_Recompile_TEST 507;

那么執行上面SQL語句,130我們確信不會導致臨時表#T去更新統計信息,501會觸發#T表的統計信息更新嗎? 如果不會觸發,那么確切的值是多少呢?答案是507,如下截圖所示:

clipboard[4]

想必有些人會說,我實驗的結果不一樣哦(啪啦啪啦說一大堆),那么你是否真正的理解了下面公式呢? n表示臨時表變跟前的記錄數(確切的說是統計信息采集時的記錄數),后面的RT表示變跟的記錄數。

Temporary table

  1. If n < 6, RT = 6.
  2. If 6 <= n <= 500, RT = 500.
  3. If n > 500, RT = 500 + 0.20 * n.

由於我第一次執行的是EXEC dbo.Usp_Recompile_TEST 1,那么數據庫的記錄數為1,那么1+ 6 =7; 也就是上圖EXEC dbo.Usp_Recompile_TEST 7時才觸發臨時表#T的統計信息更新,而為什么是507(7+500=507)呢,因為最后一次統計信息的采集,臨時表#T的記錄數為7 ,所以7+500=507,是否有點不解,那么你按我這個SQL執行一遍,然后用Profile跟蹤、你會看到下面結果,如果還不太明白,結合截圖好好理解一下:

DBCC FREEPROCCACHE;
 
EXEC dbo.Usp_Recompile_TEST 2;
 
EXEC dbo.Usp_Recompile_TEST 6;
 
EXEC dbo.Usp_Recompile_TEST 7;
 
EXEC dbo.Usp_Recompile_TEST 8;

clipboard[5]

如果還沒有理解的話,我的表達能力已到極限了,自己再好好琢磨一下吧! 那么接下來才是我們重點想要驗證、測試的。

DBCC FREEPROCCACHE; 
 
EXEC dbo.Usp_Recompile_TEST 501; 

此時臨時表#T的記錄數為501,那么當臨時表#T里面的記錄數變更了多少時,才會觸發統計信息的更新呢? 由於是插入,那么根據公式應該是501 + (500 + 0.2*501) = 1101.2 ,那么應該是1101,即使是1100也不會變化。下面SQL Server Profile可以驗證我們的推測

EXEC dbo.Usp_Recompile_TEST 1100; 
 
EXEC dbo.Usp_Recompile_TEST 1101; 

如果我們繼續使用該存儲過程,那么當參數為什么值時才會觸發統計信息更新呢? 1101 +(500+0.2*1101)=1821.2,也就是說必須是1821才會觸發統計信息更新,下面SQL Server Profile的截圖也驗證了我們的推測。

EXEC dbo.Usp_Recompile_TEST 1300; 
 
EXEC dbo.Usp_Recompile_TEST 1320; 
 
EXEC dbo.Usp_Recompile_TEST 1321; 
 
EXEC dbo.Usp_Recompile_TEST 1820; 
 
EXEC dbo.Usp_Recompile_TEST 1821;

clipboard[6]

所以綜上述實驗驗證,SQL SERVER 臨時表導致存儲過程重編譯(recompile)的那些閥值確實是正確的,也是沒有問題的。當然如有疏漏或不對的地方,敬請指出。


免責聲明!

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



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