【搬運】表變量與臨時表的優缺點

表變量:  算法

  DECLARE @tb  table(id   int   identity(1,1), name   varchar(100))   
  sql

  INSERT @tb   數據庫

 SELECT id, name  FROM mytable   WHERE name like ‘zhang%’ 緩存

 

臨時表: session

  SELECT name, address
  INTO #ta   FROM mytable  
  WHERE name like ‘zhang%’架構

if exists (select * from tempdb.dbo.sysobjects where id = object_id(N'tempdb..#ta') and type='U')
   drop table #ta)
併發

 

表變量和臨時表的比較:app

  • 臨時表是利用了硬盤(tempdb數據庫) ,表名變量是佔用內存,所以小數據量固然是內存中的表變量更快。當大數據量時,就不能用表變量了,太耗內存了。大數據量時適合用臨時表。
  • 表變量缺省放在內存,速度快,因此在觸發器,存儲過程裏若是數據量不大,應該用表變量。
  • 臨時表缺省使用硬盤,通常來講速度比較慢,那是否是就不用臨時表呢?也不是,在數據量比較大的時候,若是使用表變量,會把內存耗盡,而後使用 TEMPDB的空間,這樣主要仍是使用硬盤空間,但同時把內存基本耗盡,增長了內存調入調出的機會,反而下降速度。這種狀況建議先給TEMPDB一次分配合適的空間,而後使用臨時表。
  • 臨時表相對而言表變量主要是多了I/O時間,但少了對內存資源的佔用。數據量較大的時候,因爲對內存資源的消耗較少,使用臨時表比表變量有更好的性能。
  • 建議:觸發器、自定義函數用表變量;存儲過程看狀況,大部分用表變量;特殊的應用,大數據量的場合用臨時表。
  • 表變量有明確的做用域,在定義表變量的函數、存儲過程或批處理結束時,會自動清除表變量。
  • 在存儲過程當中使用表變量與使用臨時表相比,減小了存儲過程的從新編譯量。
  • 涉及表變量的事務只在表變量更新期間存在。這樣就減小了表變量對鎖定和記錄資源的需求。
  • 表變量須要事先知道表結構,普通臨時表,只在當前會話中可用與表變量相同into一下就能夠了,方便;全局臨時表:可在多個會話中使用存在於temp中需顯示的drop。(不知道表結構狀況下臨時表方便一些)
  • 全局臨時表的功能是表變量無法達到的。
  • 表變量沒必要刪除,也就不會有命名衝突,臨時表特別是全局臨時表用的時候必須解決命名衝突。
  • 應避免頻繁建立和刪除臨時表,減小系統表資源的消耗。
  • 在新建臨時表時,若是一次性插入數據量很大,那麼可使用select into代替create table,避免log,提升速度;若是數據量不大,爲了緩和系統表的資源,建議先create table,而後insert。
  • 若是臨時表的數據量較大,須要創建索引,那麼應該將建立臨時表和創建索引的過程放在單獨一個子存儲過程當中,這樣才能保證系統可以很好的使用到該臨時表的索引。
  • 若是使用到了臨時表,在存儲過程的最後務必將全部的臨時表顯式刪除,先truncate table,而後drop table,這樣能夠避免系統表的較長時間鎖定。
  • 慎用大的臨時表與其餘大表的鏈接查詢和修改,減低系統表負擔,由於這種操做會在一條語句中屢次使用tempdb的系統表。
--------------------------------------------------
 
問題 1:爲何在已經有了臨時表的狀況下還要引入表變量?

解答 1:
與臨時表相比,表變量具備下列優勢:
如 SQL Server 聯機叢書「表」(Table) 一文中所述,表變量(如局部變量)具備明肯定義的範圍,在該範圍結束時會自動清除這些表變量。
與臨時表相比,表變量致使存儲過程的從新編譯更少。
涉及表變量的事務僅維持表變量上更新的持續時間。所以,使用表變量時,須要鎖定和記錄資源的狀況更少。由於表變量具備有限的範圍而且不是持久性數據庫的一部分,因此事務回滾並不影響它們。
問題 2:若是說使用表變量比使用臨時表致使存儲過程的從新編譯更少,這意味着什麼?

解答 2:
下面的文章討論了從新編譯存儲過程的一些緣由:

243586  (http://support.microsoft.com/kb/243586/) 存儲過程從新編譯的疑難解答
「因爲某些臨時表操做引發的從新編譯」一節還列出了爲避免一些問題(例如使用臨時表致使從新編譯)而須要知足的一些要求。這些限制不適用於表變量。

表變量徹底獨立於建立這些表變量的批,所以,當執行 CREATE 或 ALTER 語句時,不會發生「從新解析」,而在使用臨時表時可能會發生「從新解析」。臨時表須要此「從新解析」,以便從嵌套存儲過程引用該表。表變量徹底避免了此問題,所以存儲過程可使用已編譯的計劃,從而節省了處理存儲過程的資源。

問題 3:表變量有哪些缺陷?

解答 3:
與臨時表相比,它存在下列缺陷:
在表變量上不能建立非彙集索引(爲 PRIMARY 或 UNIQUE 約束建立的系統索引除外)。與具備非彙集索引的臨時表相比,這可能會影響查詢性能。
表變量不像臨時表那樣能夠維護統計信息。在表變量上,不能經過自動建立或使用 CREATE STATISTICS 語句來建立統計信息。所以,在大表上進行復雜查詢時,缺乏統計信息可能會妨礙優化器肯定查詢的最佳計劃,從而影響該查詢的性能。
在初始 DECLARE 語句後不能更改表定義。
表變量不能在 INSERT EXEC 或 SELECT INTO 語句中使用。
表類型聲明中的檢查約束、默認值以及計算所得的列不能調用用戶定義的函數。
若是表變量是在 EXEC 語句或 sp_executesql 存儲過程外建立的,則不能使用 EXEC 語句或sp_executesql 存儲過程來運行引用該表變量的動態 SQL Server 查詢。因爲表變量只能在它們的本地做用域中引用,所以 EXEC 語句和 sp_executesql 存儲過程將在表變量的做用域以外。可是,您能夠在 EXEC 語句或 sp_executesql 存儲過程內建立表變量並執行全部處理,由於這樣表變量本地做用域將位於 EXEC 語句或 sp_executesql 存儲過程當中。
問題 4:與臨時表或永久表相比,表變量的僅存在於內存中的結構保證了更好的性能,是否由於它們是在駐留在物理磁盤上的數據庫中維護的?

解答 4:
表變量不是僅存在於內存中的結構。因爲表變量可能保留的數據較多,內存中容納不下,所以它必須在磁盤上有一個位置來存儲數據。與臨時表相似,表變量是在 tempdb 數據庫中建立的。若是有足夠的內存,則表變量和臨時表都在內存(數據緩存)中建立和處理。

問題 5:必須使用表變量來代替臨時表嗎?

解答 5:
答案取決於如下三個因素:
插入到表中的行數。
從中保存查詢的從新編譯的次數。
查詢類型及其對性能的指數和統計信息的依賴性。
在某些狀況下,可將一個具備臨時表的存儲過程拆分爲多個較小的存儲過程,以便在較小的單元上進行從新編譯。 

一般狀況下,應儘可能使用表變量,除非數據量很是大而且須要重複使用表。在這種狀況下,能夠在臨時表上建立索引以提升查詢性能。可是,各類方案可能互不相同。Microsoft 建議您作一個測試,來驗證表變量對於特定的查詢或存儲過程是否比臨時表更有效。
 
轉自:http://blog.csdn.net/leamonjxl/article/details/6602716
一直以來你們對臨時表與表變量的孰優孰劣爭論頗多,一些技術羣裏的朋友甚至認爲表變量幾乎一無可取,好比無統計信息,不支持事務等等.但事實並不是如此.這裏我就臨時表與表變量作個對比,對於大多數人不理解或是有歧義的地方進行詳細說明.
注:這裏只討論通常臨時表,對全局臨時表不作闡述.
生命週期
臨時表:會話中,proc中,或使用顯式drop
表變量:batch中
這裏用簡單的code說明表變量做用域
複製代碼
DECLARE @t TABLE(i int) ----定義表變量@t SELECT *FROM @t -----訪問OK insert into @t select 1 -----插入數據OK select * from @t -------訪問OK go -------結束批處理 select * from @t -------不在做用域出錯
複製代碼
注意:雖說sqlserver在定義表變量完成前不容許你使用定義的變量.但注意下面狀況仍然可正常運行!

 

if 'a'='b' begin DECLARE @t TABLE(i int) end SELECT *FROM @t -----仍然能夠訪問!
日誌機制
臨時表與表變量都會記錄在tempdb中記錄日誌
不一樣的是臨時表的活動日誌在事務完成前是不能截斷的.
這裏應注意的是因爲表變量不支持truncate,因此徹底清空對象結果集時臨時表有明顯優點,而表變量只能delete
事務支持
臨時表:支持
表變量:不支持
咱們經過簡單的實例加以說明
複製代碼
create table #t (i int) declare @t table(i int)
BEGIN TRAN ttt insert into #t select 1 insert into @t select 1 SELECT * FROM #t ------returns 1 rows SELECT * FROM @t ------returns 1 rows ROLLBACK tran ttt
SELECT * FROM #t -------no rows SELECT * FROM @t -------still 1 rows drop table #t ----no use drop @t in session
複製代碼

 

鎖機制(select)
臨時表 會對相關對象加IS(意向共享)鎖
表變量 會對相關對象加SCH-S(架構共享)鎖(至關於加了nolock hint) 
能夠看出雖然說鎖的影響範圍不一樣,但因爲做用域都只是會話或是batch中,臨時表的IS鎖雖然說兼容性不如表變量的SCH-S但絕大多數狀況基本無影響.
感興趣的朋友能夠用TF1200測試
索引支持
臨時表  支持
表變量  條件支持(僅SQL2014)
沒錯,在sql2014中你能夠在建立表的同時建立索引 圖1-1
注:在sql2014以前表變量只支持建立一個默認的惟一性約束
code
複製代碼
DECLARE @t TABLE ( col1 int index inx_1 CLUSTERED, col2 int index index_2 NONCLUSTERED, index index_3 NONCLUSTERED(col1,col2) )

複製代碼

                                圖1-1ide

 

用戶自定義函數(UDFs)
臨時表 不支持做爲UDF的結果集返回
表變量 支持做爲UDF的結果集返回
注:當表變量做爲UDF的結果集返回時分爲TVF(Table-Valued Function),TVP(Table-Valued Parameters)兩種類型,只有TVF支持plan cache
如圖1-2
Code
複製代碼
CREATE FUNCTION TVP_Customers (@cust nvarchar(10)) RETURNS TABLE AS RETURN (SELECT RowNum, CustomerID, OrderDate, ShipCountry FROM BigOrders WHERE CustomerID = @cust); GO CREATE FUNCTION TVF_Customers (@cust nvarchar(10)) RETURNS @T TABLE (RowNum int, CustomerID nchar(10), OrderDate date, ShipCountry nvarchar(30)) AS BEGIN INSERT INTO @T SELECT RowNum, CustomerID, OrderDate, ShipCountry FROM BigOrders WHERE CustomerID = @cust RETURN END; DBCC FREEPROCCACHE GO SELECT * FROM TVF_Customers('CENTC'); GO SELECT * FROM TVP_Customers('CENTC'); GO SELECT * FROM TVF_Customers('SAVEA'); GO SELECT * FROM TVP_Customers('SAVEA'); GO select b.text,a.execution_count,a.* from sys.dm_exec_query_stats a cross apply sys.dm_exec_sql_text(a.sql_handle) b where b.text like '%_Customers%'
複製代碼

 

                                                                        圖1-2函數

 

其它方面
表變量不支持select into,alter,truncate,dbcc等
表變量不支持table hint 如(force seek)

 

執行計劃預估
我想這裏多是引發使用何種方式爭論比較突出的地方,因爲表變量沒有統計信息,沒法添加索引等使得你們對其在執行計劃中的性能表現嗤之以鼻,但實際狀況呢?咱們須要深刻分析.
關於臨時表的預估這裏我就不作介紹了,主要對錶變量的預估作詳細闡述.
表變量在sql2000引入的一個緣由就是爲了在一些執行過程當中減小重編譯.以得到更好的性能.固然帶來好處的同時也會帶來必定弊端.因爲其不涉及重編譯,優化器其實並不知道表變量中的具體行數,此時他採起了保守的預估方式:預估行數爲1行.如圖2-1

 Code

複製代碼
declare @t table (i int) select * from @t-----此時0行預估行數爲1行 insert into @t select 1 select * from @t-----此時1行,預估行數仍爲1行 insert into @t values (2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(14),(15),(16),(17),(18),(19),(20) select * from @t ----此時19行,預估行數仍爲1行 --....不管實際@t中有多少行,因爲沒有重編譯,預估均爲1行
複製代碼

 

 

                                                                             圖2-1

 因此當咱們加上重編譯的的操做,此時優化器就知道了表變量的具體行數.如圖2-2

Code

 

複製代碼
declare @t table (i int) select * from @t option(recompile)-----此時0行預估行數爲1行 insert into @t select 1 select * from @t option(recompile)-----此時1行,預估行數爲1行 insert into @t values (2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(14),(15),(16),(17),(18),(19),(20) select * from @t option(recompile)----此時19行,預估行數爲19行 --....當加入重編譯hint時,優化器就知道的表變量的行數.
複製代碼

 

                                                                     圖2-2

 

至此,咱們能夠看到優化器知道了表變量中的行數.這樣在表變量掃描的過程當中,尤爲針對數據量較大的情形,不會由於預估老是1而引發一些問題.
若是你剛知道這裏的預估原理,現有的代碼都加上重編譯那工做量可想而知了..這裏介紹一個新的跟蹤標記,Trace Flag 2453.
TF2453能夠必定程度上替代重編譯Hint,但只是在非簡單計劃(trivial plans)的情形下
注:TF2453只在sql2012 SP2和SQL2014中的補丁中起做用

表變量謂詞預估
因爲表變量木有統計信息,在優化器知道總體行數的前提下將會根據謂詞的情形
採用不一樣的規則"猜"來進行預估.
注:這裏有些規則筆者未找到微軟相應的算法文檔,通過本身根據數據推算得出.
看到這裏的朋友請爲我點個贊J(很長時間推算得出.可能數學忘得差很少了)
注:因爲檢索對象自己及爲變量,謂詞爲變量,或是常數無影響
常見謂詞下預估算法:
a ">", "<" 運算符 按照表變量數據量的30%進行預估
b "like" 運算符 按照表變量數據量的10%進行預估
c "="  運算符 按照表變量數據量的0.75次方預估
實例如圖2-3
code
複製代碼
declare @i int set @i=13 DECLARE @T TABLE(I INT); INSERT INTO @T VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(14),(15),(16),(17),(18),(19),(20) ------表變量中存在個數字 select * from @T where I < 1 option(recompile) ------20*30% 預估數爲6 select * from @T where I > @i option(recompile) --------20*30%預估數爲6 select * from @T where I like @i option(recompile) --------20*10% 預估數爲2 select * from @T where I like 1 option(recompile) --------20*10 預估數爲2 select * from @T where I = @i option(recompile) --------POWER(20.00000,0.75) 預估數爲9.45742 select * from @T where I = 1 option(recompile) --------POWER(20.00000,0.75) 預估數爲9.45742 insert into @T select DatabaseLogID from AdventureWorks2008R2.dbo.DatabaseLog------insert new records select * from @T option(recompile) ------------此時數據爲行 select * from @T where I = 1 option(recompile)--------------------POWER(1617.00000,0.75) 預估數爲254.99550
複製代碼

 

                                                                         圖2-3

 能夠看出根據不一樣的謂詞優化器會採用不一樣的預估方式,雖然它不如統計信息下的密度,直方圖等來的精確(尤爲是等值預估,在數據量巨大的情形下,其效果可能接近統計信息),但在瞭解數據的前提下若是適合表變量咱們仍是能夠大膽使用的.

Tempdb競爭
tempdb的競爭自己涵蓋的知識面比較大,這裏咱們只討論臨時表與表變量的孰優孰劣.
經過前面的介紹咱們知道臨時表是支持事務的,而表變量時不支持的.正因如此不少人放棄了表變量的使用.但任何事情都有兩方面,支持就必定好嗎?因爲臨時表對事務的支持,在高併發的情形中可能正由於其事務的支持形成系統表鎖,總而影響併發.
 
咱們經過一個簡單的實例來講明

平常管理中,我發現不少開發人員在使用臨時表時採用select * into #t from …的語法,這樣的寫法若是數據量稍大,將會形成事務持有系統表鎖的時間變長,從而影響併發,吞吐.咱們經過一個簡單的實例說明.如圖3-1

 

Code 咱們經過sqlquerystress模擬併發

複製代碼
----SSMS測試數據 Use tempdb create table t ( id int identity,str1 char(8000))----more pages for many records insert into t select 'a' go 100 ----sqlquerystress select * into #t from t----57s ----sqlquerystress declare @t table ( id int,str1 char(8000)) insert into @t select * from t-----1s
複製代碼

 

                                                                           圖3-1

 

經過圖3-1能夠看出上述情形中臨時表簡直不堪重負.臨時表與表變量到底該如何應用不是看誰比誰的優勢多,應視具體情形而定

搬運地址:https://blog.csdn.net/gordennizaicunzai/article/details/50760950

相關文章
相關標籤/搜索