表變量: 算法
DECLARE @tb table(id int identity(1,1), name varchar(100))
sqlINSERT @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
• | 如 SQL Server 聯機叢書「表」(Table) 一文中所述,表變量(如局部變量)具備明肯定義的範圍,在該範圍結束時會自動清除這些表變量。 |
• | 與臨時表相比,表變量致使存儲過程的從新編譯更少。 |
• | 涉及表變量的事務僅維持表變量上更新的持續時間。所以,使用表變量時,須要鎖定和記錄資源的狀況更少。由於表變量具備有限的範圍而且不是持久性數據庫的一部分,因此事務回滾並不影響它們。 |
• | 在表變量上不能建立非彙集索引(爲 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 存儲過程當中。 |
• | 插入到表中的行數。 |
• | 從中保存查詢的從新編譯的次數。 |
• | 查詢類型及其對性能的指數和統計信息的依賴性。 |
一直以來你們對臨時表與表變量的孰優孰劣爭論頗多,一些技術羣裏的朋友甚至認爲表變量幾乎一無可取,好比無統計信息,不支持事務等等.但事實並不是如此.這裏我就臨時表與表變量作個對比,對於大多數人不理解或是有歧義的地方進行詳細說明.
注:這裏只討論通常臨時表,對全局臨時表不作闡述.
生命週期
臨時表:會話中,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