曾經在網絡上看到過一種說法,SqlServer的存儲過程中使用臨時表,會導致重編譯,以至於執行計划無法重用,
運行時候會導致重編譯的這么一個說法,自己私底下去做測試的時候,根據profile的跟蹤結果,
存儲過程中使用臨時表,如果不是統計信息變更導致導致的重編譯,並不會導致重編譯,
但是現實情況下,對於一些特殊的情況,即便是統計信息沒有更新,又確實會出現每次運行都重編譯的情況,
存儲過程中使用了臨時表,什么情況下會重編譯,什么情況下不用重編譯?
為了弄清楚這個問題,查閱了大量的資料,才把這個問題弄清楚,這里特意記錄下來,
希望武斷地認為存儲過程中使用了臨時表就會導致重編譯的這個觀點得到糾正。
首先進行下面的測試,我們知道,導致臨時表重編譯的因素之一就是統計信息的變化,統計信息的變化依賴於往臨時表中寫入的數據量,
首選我要控制插入臨時表中的數據量不超過統計信息更新而導致重編譯的閥值,先排除統計信息的變更導致重編譯,
看看僅僅是多次運行SP,是否因為存儲過程中有了臨時表而會產生重編譯
--首選創建一個表,供存儲過程中測試使用 create table test1 ( id int identity(1,1), name varchar(50) ) --插入10000條測試數據 insert into test1 values (NEWID()) go 10000 --創建一個存儲過程,其中存儲過程中定義了一個臨時表,根據參數,往臨時表中寫入數據 create proc testRecompile(@i int) as begin create table #t (id int,name varchar(50)) insert into #t select id,name from test1 where id<@i select * from #t end
那么就開始運行這個SP,然后監控profile,看看第一次運行,以及除了第一次運行之后,到底有沒有發生重編譯
--第一次運行,代入參數1 exec testRecompile 1 --第二次運行,代入參數2 exec testRecompile 2
下面是profile的截圖,可以很清楚地看到,第一次運行之后,再次運行SP的時候,沒有發生重編譯的動作,也就是說重用了第一次的執行計划緩存

這里解釋兩個問題,
1,第一次運行的時候,為什么不是因為架構更改導致的重編譯,而是Deferred Compile?
2,第二次運行的時候,為什么沒有重編譯,因為臨時表是每次運行的時候創建的啊,肯定是更改了架構(change schema)了,為什么沒有重編譯?
首先,說明第一個問題,
1,第一次運行的時候,當存儲過程testRecompile編譯的時候,
插入語句(insert into #t select id,name from test1 where id<@i)和查詢語句(select * from #t),
因為#t表還沒有被創建,因為這兩句並沒有被編譯,
編譯的時候的執行計划並沒有完全完成,
當這個存儲過程執行的時候,臨時表才被創建,此時才真正的開始編譯臨時表對象的語句,這個編譯的過程是執行的時候完成的,而不是純粹的編譯階段完成的
所以這是Deferred Compile,也即是運行時才進行的編譯,就是所謂的延遲編譯(Deferred Compile)。
2,第二個問題,重新運行臨時表的時候,按道理,因為創建了臨時表,必然導致架構的更改,為什么沒有重編譯?
這個是因為,存儲過程中使用了臨時表,對臨時表的使用是引用其“名稱”(比如這里的#t),而非ID(從臨時數據庫中查詢sys.sysobjects)
雖然多個會話同時運行這個SP的話,每個會話都會生成一個臨時表,每個會話生成的臨時表的ID都是不同的,
但是要注意的是,存儲過程中並沒有直接使用臨時表對象的ID,而是臨時表名字本身,
第一次運行之后,緩存的執行計划與第二次運行時一樣的,所以第二次運行這個SP可以重用這個第一次生成的執行計划,
上面說了,在某些情況下,存儲過程中使用臨時表會導致重編譯,這是在什么情況下發生的呢?
因為在某些情況下,要先生成臨時表,然后以動態sql的方式去執行一段有臨時表參與的sql,此時對於臨時表的引用是引用其ID,而不是名稱
這個要歸結於對於臨時表的調用方式,當存儲過程中定義了臨時表,用sp_executesql的方式調用的時候,這兩種執行sql的方式相當於新建了會話,
此時因為不同回話之間,同一個臨時表生成的ID是不同的,此時才會導致存儲過程中發生sechme change的重編譯
上代碼
create proc testRecompile2(@i int)
as
begin
create table #t (id int,name varchar(50))
insert into #t select id,name from test1 where id<=@i
exec('select * from #t')
end
DBCC FREEPROCCACHE
--第一次運行,代入參數1
exec testRecompile2 1
--第二次運行,代入參數2
exec testRecompile2 2

在存儲過程中創建了臨時表,執行的時候到底發生不發生重編譯,取決於你怎么使用這個臨時表
以sp_executesql的方式執行臨時表的sql的時候,才會發生因為schema change導致的重編譯,
因為這兩種方式執行sql,相當於新建會話去執行sql,此時對於臨時表的引用,是引用臨時表生成的ID,不同會話之間的臨時表對象的ID是不同的,所以無法重用執行計划,會發生重編譯
另外,對於臨時表的另一種導致重編譯的因素就是統計信息,對於統計信息變更導致的重編譯,就不多說了,這個不僅僅會發生在臨時表上,普通的物理表上也會因為統計信息變更導致重編譯,不止是臨時表,唯一的區別就是,導致臨時表與物理表統計信息變更的閥值是不一樣的
另外,對於統計信息變更導致的重編譯,就不多說了,這個不僅僅會發生在臨時表上,普通的物理表上也會因為統計信息變更導致重編譯,不止是臨時表,唯一的區別就是,導致臨時表與物理表統計信息變更的閥值是不一樣的
我們知道
這個也很容易驗證,臨時表統計信息更新的閥值依賴於臨時表中數據的變化幅度,這個閥值如下
If n < 6, Recompilation threshold = 6.
If 6 <= n <= 500, Recompilation threshold = 500.
If n > 500, Recompilation threshold = 500 + 0.20 * n.
