1. 程式人生 > >SQL Server資料庫的儲存過程中定義的臨時表,真的有必要顯式刪除(drop table #tableName)嗎?

SQL Server資料庫的儲存過程中定義的臨時表,真的有必要顯式刪除(drop table #tableName)嗎?

問題背景

在寫SQL Server儲存過程中,如果儲存過程中定義了臨時表,
有些人習慣在儲存過程結束的時候一個一個顯式地刪除過程中定義的臨時表(drop table #tName),有些人又沒有這個習慣,
對於不明真相的群眾或者喜歡思考的人會問,儲存過程中定義的臨時表,最後要不要主動刪除,為什麼?
或者說是不是儲存過程結束的時候刪除臨時表更加規範?
不止一個人問過這個問題了,說實在話,本人之前確實不清楚,只是認為,顯式刪掉或者不刪都行,臨時表在當前Session斷開之後會自動釋放
那麼儲存過程中定義的臨時表,在使用完之後,到底刪還是不刪?顯式刪除與不做刪除有無區別?
本文將對此問題進行一個粗淺的分析,如有不對的地方,還望指出,謝謝。

儲存過程中臨時表的表結構也有緩並且會被重用

那麼到底需不需要顯式刪除,顯式刪除或者是不刪除有什麼區別?
這中間涉及到一個臨時表快取的知識點,首先看什麼是臨時表的快取。
快取臨時表是SQL SERVER 2005以來的一個新特性,
臨時表的建立時需要往臨時庫系統表中寫入資料(元資料,臨時表的表結構資訊),跟普通的增刪改操作一樣,這個過程需要一定的資源消耗
在滿足一定條件的情況下(後面說需要滿足的條件是什麼),
每當使用者請求完成之後(當然這個使用者請求的SQL中包含了臨時表),臨時表的元資料將會儲存在臨時庫(tempdb)的系統表中
雖然在使用者看來,當前Session建立的臨時表,對其他Session事不可見的,在Session斷開或者臨時表被刪除(drop)之後,將不可訪問。
但是當新的Session呼叫同樣的包含了建立臨時表的程式碼,SQL Server內部會重用之前Session執行時建立過的臨時表,而無需再次定義臨時表。
這樣的話可以節約一些建立表的步驟所消耗的資源。

上面是理論,下面來做個小實驗演示上面的理論,首先來看不同Session之間臨時表“重用”的現象。
首先這裡要藉助系統檢視sys.dm_os_performance_counters 來判斷臨時表的建立次數,該系統表中計數器的名稱為:Temp Tables Creation Rate。

建立如下儲存過程,儲存過程中定義了一個臨時表,

create procedure Proc_TestTempTable
as
begin
    
    create table #t20170413
    (
        col_1 varchar(100) ,
        col_2 
varchar(100) ) insert into #t20170413 values ('aaa','bbb'); select * from #t20170413 --select * from tempdb.sys.tables where name like '#t20170413%' end

在儲存過程建立之後,第一次執行的時候,來觀察一個現象,如下截圖

很明顯,sys.dm_os_performance_counters系統表中的Temp Tables Creation Rate計數器加了1,也就是說在執行儲存過程中過程中發生了一次臨時表的建立動作
然後繼續再次執行上面的程式碼

同樣的程式碼,這一次sys.dm_os_performance_counters系統表中的Temp Tables Creation Rate計數器沒有加1,
為什麼明明是儲存過程中定義了臨時表,上面執行一次,Temp Tables Creation Rate加1,然後再次執行就不加1了?
這個就是臨時表重用的現象(嚴格說是臨時表的表結構或者表定義,而不包含資料),
因為第一次執行儲存過程的時候建立了臨時表,然後再次執行儲存過程的時候就重用了第一次的臨時表。  

  那怎麼證明該儲存過程第二次執行的時候重用了第一次建立的臨時表?
  對儲存過程稍作修改,儲存過程中加一句程式碼,查詢臨時庫中該臨時表資訊

  然後執行兩次如下程式碼,下面截圖是第二次執行的結果(下面會做解釋為什麼是第二次的執行的結果),
  在臨時表被重用的時候查詢出來當前臨時表的資訊,發現臨時表建立次數並沒有增加,也就是說臨時表被重用了

  既然說臨時表重用了,那麼臨時表一定存在於臨時庫的系統表中,那麼如何證明這個儲存過程的臨時表在臨時庫中呢?
  上面顯示的臨時表的Id是-1297292959,那麼這裡就臨時庫中查詢Id = -1297292959的表資訊,發現果然存在這個一張表。
  臨時庫中的這個表資訊除了名字和modify_date不一樣,modify_date據觀察是臨時表被重用的時間,也就是臨時表被重用一次就修改一次modify_date
  其他資訊完全一致,這就是說明,儲存過程第一次執行完成之後,它所建立的臨時表被快取了起來(至於名字不同,後面再解釋),
  當再次執行該儲存過程的時候可以重用第一次執行儲存過程時候建立的臨時表的表結構。

儲存過程中顯式刪除臨時表,到底有沒有用處?

對上面的儲存過程做如下修改,在儲存過程結束之前顯式刪除定義的臨時表

  然後再次執行如下的測試程式碼,注意截圖是第二次執行的結果(下面會做解釋為什麼是第二次的執行的結果)

  然後繼續在臨時庫的系統表中查詢上述Id的系統,發現臨時表依舊存在於系統表中,即便是儲存過程中顯式刪除(drop table #t20170413)

  這裡說明,即便在儲存過程中顯式呼叫了刪除臨時表的操作,臨時表依舊會存在得臨時庫的系統表中,也就是說臨時表依舊會被快取。
  並不會因為在儲存過程中顯式刪除而真正的刪除,臨時表物件會快取在臨時庫的系統表中。
  之所以Session中查詢到的臨時表的名字與系統表中查詢到的臨時表的名字不同,
  原因是臨時表從建立到快取(當前Session斷開之後),在內部只是發生了一個對當前Session臨時表重新命名的過程。
  被快取的臨時表的重用的過程與上面的類似,也是將快取的換反向重新命名。

事實證明:
對於儲存過程的臨時表,在滿足可快取的前提下(只是表結構,當然不包括臨時表的資料),
你刪,或者不刪,他都會快取在臨時庫中,並不因為顯式Drop臨時表,臨時表就會被真正的刪除,這是SQL Server專門為此做的優化,你真的不用為刪除臨時表而操心或者糾結
這裡回到一開始的問題,儲存過程中有沒有必要顯式刪除臨時表就有答案了:對於儲存過程的建立的臨時表,沒必要刪除,對於滿足可快取的臨時表物件,想刪也刪不掉!

儲存過程中定義的臨時表,只有滿足一定的條件,才會被快取重用

  上面說了,臨時表的重用是要滿足一定條件的,如下條件將會導致臨時表無法重用

1,建立臨時表的時候存在命名約束(這一點非常操蛋,不僅僅是快取問題,曾經遇到過坑,有機會演示)
2,在臨時表建立之後執行DDL操作,比如建立索引等,但是這個DDL不包括drop 臨時表和truncate臨時表
3,動態SQL方式建立的臨時表
4,在不同的範圍之內建立的臨時表,應該是儲存過程呼叫另外一個儲存過程,另外一個儲存過程定義的臨時表,這一點還沒有具體研究
5,儲存過程以WITH RECOMPILE重編譯的方式執行


  比如在上面的儲存過程,在臨時表定義之後,建立一個索引,
  此舉將會造成臨時表無法重用,這種情況下,不管你刪或者不刪,儲存過程執行完成Session斷開之後,臨時表都不會快取(在臨時庫中)
  這一點就不截圖演示了,有興趣的自己測試

  解釋另外一個問題:
  既然認為無法刪除快取的臨時表,正常情況下,快取的臨時表什麼情況下會被刪除?
  上面說截圖都是第二次執行的截圖,因為在儲存過程重建之後(create或者alter),這個儲存過程中定義的臨時表都會被清理掉
  只有重建了儲存過程,第一次執行之後,快取的臨時表在第二次執行的時候才能被重用
  當然這一點也和容易驗證,快取臨時表之後,然後alter 儲存過程,
  然後根據快取臨時表的Id去查詢臨時庫中sys .tables的資訊,這個快取的表會在1~2秒之後被刪除(個人測試驗證過)
  另外顯式執行DBCC FREEPROCCACHE,也能刪除快取的臨時表。
  其實也不難理解,快取的物件是跟執行計劃快取繫結的,如果執行計劃本身就不存在了,那麼快取的臨時表物件也將會被請處理。

併發執行的情況下,臨時表能否重用?

  併發執行緒之間當然不會重用同一個臨時表,如果不是這樣的話,SQL Server也不用混江湖了,併發的每個執行緒會建立自己的臨時表。
  參考如下截圖是在併發情況下,tempdb產生的臨時表的情況,每個執行緒呼叫儲存過程產生的臨時表字尾都是不一樣的。
  併發呼叫儲存過程的時候,每個執行緒會產生屬於自己的臨時表,重用臨時表是發生在當前執行緒執行完成之後,其他Session重新呼叫儲存過程時候才能重用已快取的臨時表。
  鑑於本文不是專門說明臨時表的,這裡就不多說了。

  

顯式刪除臨時表與否的效能測試

既然上面說了,如果儲存過程中定義的臨時表滿足臨時表被快取的條件的情況下,儲存過程中是否刪除臨時表,臨時表都一樣會被快取
那麼,如果真的指定了顯式刪除臨時表操作,與沒有顯式指定刪除臨時表,效能上有沒有差別呢?
抱著以資料說話的態度,分別在儲存過程中不刪除與顯式刪除臨時表,用SQLQueryStress做了一系列的效能測試
結果如下

不顯式刪除臨時表 顯式刪除臨時表

測試結果如下, 

  

  測試過程部分截圖(不浪費部落格園的圖片伺服器資源了,隨便截了兩張)

  

  從測試結果看,確實有一些差異,不過這個差別是非常小的,
  第一組測試結果5000次呼叫產生了0.07秒的差距
  第二組測試結果20000次呼叫產生了0.35秒的差距,平均到一次差距也就在微妙級,即便是顯式呼叫刪除,對效能來說是有一點點影響,不過這個影響也是無傷大雅。
  不過這個內部的原始一定要弄清楚,有沒有必要刪除,以及原因,這個才是原則性的問題!

    至於臨時表資料佔用的空間,也不是說顯式刪除就釋放,不刪除就不釋放,應該是有後臺程序來做這個工作的,個人建議不用為這個問題瞎操心。
  寫儲存過程的時候,多寫一點好一點的SQL語句,比糾結這個強多了。

多囉嗦一句:
有些人的觀念是根深蒂固的,對於習慣刪除臨時表的人,覺得這麼做“規範”,“專業”,雖然他沒有確切的理由說明顯式刪除臨時表的必要性。
但是你要跟他說沒必要刪除臨時表,一定會激怒他,好多程式設計師都是這樣的,你否認他根深蒂固的一個觀點的時候,他是很惱火的。
從生物學上說,這個是屬於“印隨行為”,如宗教般,在自己處於懵懂期的時候,受到一些說法的影響
或許是當初的師傅說的,或者膜拜的物件這麼做了,或者聽高人說過這麼做比較好,然後自己就一直這麼做了並且堅信不疑。
當然,包括我自己在某些時候也有此種行為,思維被曾經的某一些經歷固化,然後一直束縛自己的認知。
不過對於無傷大雅的問題,就隨他去了,沒必要說服他,弄不好他反過來覺得你業餘,希望小夥伴們明辨,好似乎跑題了……

顯式刪除臨時表與否與臨時庫空間釋放問題

  有人擔心說,如果不顯式刪除臨時表,是不是臨時表佔用的空間無法快速釋放?
  其實也不用顧慮,還是以資料說話,這裡對比兩個一樣的儲存過程,一個不顯式刪除臨時表,一個顯示刪除臨時表,看看臨時資料庫中使用者物件佔用page的情況
  不顯式刪除臨時表的儲存過程
  做如下對比測試,藉助SQLQueryStress,做一個20執行緒,每個執行緒500次迴圈的測試

      

  測試的過程中,在臨時資料庫中,利用如下SQL,間隔一秒的頻率抓取臨時庫中user objects物件的資料

  

  把上述指令碼記錄到的資料,利用Excel的透檢視功能,呈現出來上述指令碼記錄到的user objects數量,可以很清楚地發現,不顯式刪除臨時表,與顯式刪除臨時表相比,UserObjecs數量並沒有明顯的差異
  也就數說,不顯式刪除臨時表的情況下,並沒有出現臨時表空間物件釋放不及時的情況

  

   因此大可不必擔心,不顯式刪除臨時表,臨時表申請的空間無法及時釋放。

總結: 

  本文從儲存過程中的臨時表是否需要顯式刪除入手,簡單介紹了臨時表重用的現象和前提條件,以及有無必要顯式刪除臨時表,
  同時測試了臨時表在滿足重用的情況下,臨時表顯式刪除與否的效能問題,對於儲存過程中定義的臨時表,不管是否能否快取重用,都不建議顯式刪除。