今天在我和一家軟件公司的開發人員討論數據庫設計調優的時候又討論到了表變量和臨時表的問題,覺得這個問題確實是一個爭議比較大的問題。
其實從上次發表了表變量和臨時表的一個帖子http://database.ctocio.com.cn/tips/442/8206442.shtml以來,也有些人留言,也有些人發過郵件討論這個問題。其實表變量和臨時表的區別雖然有一些,但是兩者最根本的區別還是在於
對存儲的需求:表變量和臨時表都消耗Tempdb中的存儲空間,但是進行數據更新的時候,表變量不會寫日志,而臨時表則會寫日志。(這一點是經過腳本測試的,表變量並不像我們想象的那樣,只寫在內存而不出現在Tempdb中。)
對優化的支持:表變量不支持索引和統計數據,臨時表則可以支持索引和統計數據。
通常需要表變量或者臨時表的情況都是一些需要支持臨時計算結果集的地方,那麼就有一些常見的情況了:
如果臨時結果集僅僅需要往裡面寫數據,比如通過一個循環多次查找相關數據並合成一個臨時結果集,那麼就可以使用表變量。(結果有人提到了返回結果集的時候需要有排序,但是表變量不支持索引阿。其實這個不要緊,因為表變量雖然不支持索引,但是表變量支持主鍵阿,所以可以利用主鍵來替代索引。)
如果臨時結果集不太多需要更改,而是更多地充當一個臨時的關聯數據集去參加各種數據集的連接(JOIN),那麼索引和統計數據可能會更加適合一些(當然這個臨時結果集要足夠大,這樣索引和統計數據帶來的代價才可以被彌補掉)。
由於表變量不支持統計數據,因此在一個存儲過程中使用表變量可以減少由於數據變化而導致的重新編譯問題。
當然,除了索引和統計數據這個明顯的限制外,表變量同時也不支持並行執行計劃,因此對於大型的臨時結果集,表變量也不是一個好的選擇。
前面一個關於表變量和臨時表的貼子,有一位robi_xu的朋友提到的問題也確實是在選擇表變量和臨時表時候的一些問題。
對於函數中不能支持臨時表是由於函數不能對函數作用域外部的資源狀態造成永久性的更改,在SQL Server中也稱為副作用(side effect)。不過如果在函數中使用大型的臨時結果集是不推薦的,因為如果將這樣的函數放置到一個查詢中會造成很明顯的性能問題,因此這種情況一般都采用存儲過程之類的批處理腳本。
對於動態腳本不支持表變量的原因是因為存儲過程不接受表類型的參數。不過如果表變量的聲明和賦值都在sp_executesql的參數中的話,sp_executesql就可以執行了,因為這個時候表變量就存在sp_executesql的stmt參數裡面,不需要傳入,例如下面的代碼:(當然這樣的實用性也就沒有多少了)
DECLARE @m nvarchar(max)
SET @m = N'DECLARE @t TABLE (ID int);INSERT INTO @t VALUES (1);SELECT * FROM @t T'
EXEC sp_executesql @m