表变量: 算法
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