SQL Server中的临时表和表variables有什么区别?

在SQL Server 2005中,我们可以通过两种方式创build临时表:

declare @tmp table (Col1 int, Col2 int); 

要么

 create table #tmp (Col1 int, Col2 int); 

这两者有什么区别? 对于@tmp是否仍然使用tempdb,或者内存中是否发生任何事情,我都读过了相互冲突的意见。

在哪种情况下,一个performance优于另一个?

临时表(#tmp)和表variables(@tmp)之间有一些区别,尽pipe使用tempdb不是其中之一,正如下面的MSDN链接中所述。

作为一个经验法则,对于小到中等数量的数据和简单的使用场景,您应该使用表variables。 (这是一个过于宽泛的指导方针,当然还有很多例外情况 – 请参阅下文和以下文章。)

在select它们时需要考虑的一些要点:

  • 临时表是真正的表,所以你可以做一些事情,比如CREATE INDEXes等。如果你有大量的数据,索引访问速度会更快,那么临时表是一个不错的select。

  • 表variables可以使用PRIMARY KEY或UNIQUE约束具有索引。 (如果你想要一个非唯一索引,只需要把主键列作为唯一约束的最后一列,如果你没有唯一的列,你可以使用一个标识列) .SQL 2014也有非唯一索引 。

  • 表variables不参与事务,而SELECT则隐含在NOLOCK 。 事务行为可以是非常有用的,例如,如果你想通过一个过程中途ROLLBACK,那么在该事务中填充的表variables将仍然被填充!

  • 临时表可能会导致重新编译存储过程,也许经常。 表variables不会。

  • 您可以使用SELECT INTO创build一个临时表,它可以更快地写入(适用于即席查询),并且可以让您随着时间的推移处理不断变化的数据types,因为您不需要预先定义临时表结构。

  • 您可以从函数中传回表variables,使您能够更容易地封装和重用逻辑(例如,创build函数将string拆分为某个任意分隔符的值表)。

  • 在用户定义函数中使用表variables可以使这些函数更广泛地使用(详情请参阅CREATE FUNCTION文档)。 如果你正在编写一个函数,你应该在临时表中使用表variables,除非有其他需要。

  • 表variables和临时表都存储在tempdb中。 但是表variables(自2005年以来)默认为当前数据库与临时表(使用tempdb( ref )的默认sorting规则)的sorting规则。 这意味着如果使用临时表,并且您的数据库归类与tempdb不同,则应该知道归类问题,如果要将临时表中的数据与数据库中的数据进行比较,则会导致问题。

  • 全局临时表(## tmp)是可供所有会话和用户使用的另一种临时表。

进一步阅读:

  • 马丁·史密斯在dba.stackexchange.com上的杰出回答

  • MSDN常见问题解答: https : //support.microsoft.com/en-gb/kb/305977

  • MDSN博客文章: http : //blogs.msdn.com/sqlserverstorageengine/archive/2008/03/30/sql-server-table-variable-vs-local-temporary-table.aspx

  • 文章: http : //searchsqlserver.techtarget.com/tip/0,289483,sid87_gci1267047,00.html#

只要看看接受的答案中的表格variables不参与logging。

一般来说,logging数量是有差异的(至less对于表格本身是insert / update / delete操作,尽pipe我已经发现在这方面存储过程中caching的临时对象有一些细微的差别,额外的系统表更新)。

我查看了针对以下操作的针对@table_variable#temp表的日志logging行为。

  1. 插入成功
  2. 多行插入由于违反约束而回滚的语句。
  3. 更新
  4. 删除
  5. 取消分配

事务日志logging对于所有操作几乎是相同的。

表variables版本实际上有一些额外的日志条目,因为它得到一个条目添加到(并且以后从sys.syssingleobjrefs基表中删除),但总体上有几个更less的字节logging纯粹是因为表variables的内部名称消耗less236字节比#temp表(less于118个nvarchar字符)。

完整的脚本来重现(最好运行在单用户模式下启动并使用sqlcmd模式的实例)

 :setvar tablename "@T" :setvar tablescript "DECLARE @T TABLE" /* --Uncomment this section to test a #temp table :setvar tablename "#T" :setvar tablescript "CREATE TABLE #T" */ USE tempdb GO CHECKPOINT DECLARE @LSN NVARCHAR(25) SELECT @LSN = MAX([Current LSN]) FROM fn_dblog(null, null) EXEC(N'BEGIN TRAN StartBatch SAVE TRAN StartBatch COMMIT $(tablescript) ( [4CA996AC-C7E1-48B5-B48A-E721E7A435F0] INT PRIMARY KEY DEFAULT 0, InRowFiller char(7000) DEFAULT ''A'', OffRowFiller varchar(8000) DEFAULT REPLICATE(''B'',8000), LOBFiller varchar(max) DEFAULT REPLICATE(cast(''C'' as varchar(max)),10000) ) BEGIN TRAN InsertFirstRow SAVE TRAN InsertFirstRow COMMIT INSERT INTO $(tablename) DEFAULT VALUES BEGIN TRAN Insert9Rows SAVE TRAN Insert9Rows COMMIT INSERT INTO $(tablename) ([4CA996AC-C7E1-48B5-B48A-E721E7A435F0]) SELECT TOP 9 ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM sys.all_columns BEGIN TRAN InsertFailure SAVE TRAN InsertFailure COMMIT /*Try and Insert 10 rows, the 10th one will cause a constraint violation*/ BEGIN TRY INSERT INTO $(tablename) ([4CA996AC-C7E1-48B5-B48A-E721E7A435F0]) SELECT TOP (10) (10 + ROW_NUMBER() OVER (ORDER BY (SELECT 0))) % 20 FROM sys.all_columns END TRY BEGIN CATCH PRINT ERROR_MESSAGE() END CATCH BEGIN TRAN Update10Rows SAVE TRAN Update10Rows COMMIT UPDATE $(tablename) SET InRowFiller = LOWER(InRowFiller), OffRowFiller =LOWER(OffRowFiller), LOBFiller =LOWER(LOBFiller) BEGIN TRAN Delete10Rows SAVE TRAN Delete10Rows COMMIT DELETE FROM $(tablename) BEGIN TRAN AfterDelete SAVE TRAN AfterDelete COMMIT BEGIN TRAN EndBatch SAVE TRAN EndBatch COMMIT') DECLARE @LSN_HEX NVARCHAR(25) = CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 1, 8),2) AS INT) AS VARCHAR) + ':' + CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 10, 8),2) AS INT) AS VARCHAR) + ':' + CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 19, 4),2) AS INT) AS VARCHAR) SELECT [Operation], [Context], [AllocUnitName], [Transaction Name], [Description] FROM fn_dblog(@LSN_HEX, null) AS D WHERE [Current LSN] > @LSN SELECT CASE WHEN GROUPING(Operation) = 1 THEN 'Total' ELSE Operation END AS Operation, Context, AllocUnitName, COALESCE(SUM([Log Record Length]), 0) AS [Size in Bytes], COUNT(*) AS Cnt FROM fn_dblog(@LSN_HEX, null) AS D WHERE [Current LSN] > @LSN GROUP BY GROUPING SETS((Operation, Context, AllocUnitName),()) 

结果

 +-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+ | | | | @TV | #TV | | +-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+ | Operation | Context | AllocUnitName | Size in Bytes | Cnt | Size in Bytes | Cnt | Difference Bytes | +-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+ | LOP_ABORT_XACT | LCX_NULL | | 52 | 1 | 52 | 1 | | | LOP_BEGIN_XACT | LCX_NULL | | 6056 | 50 | 6056 | 50 | | | LOP_COMMIT_XACT | LCX_NULL | | 2548 | 49 | 2548 | 49 | | | LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysallocunits.clust | 624 | 3 | 624 | 3 | | | LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrowsets.clust | 208 | 1 | 208 | 1 | | | LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst | 832 | 4 | 832 | 4 | | | LOP_CREATE_ALLOCCHAIN | LCX_NULL | | 120 | 3 | 120 | 3 | | | LOP_DELETE_ROWS | LCX_INDEX_INTERIOR | Unknown Alloc Unit | 720 | 9 | 720 | 9 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysallocunits.clust | 444 | 3 | 444 | 3 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysallocunits.nc | 276 | 3 | 276 | 3 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.syscolpars.clst | 628 | 4 | 628 | 4 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.syscolpars.nc | 484 | 4 | 484 | 4 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysidxstats.clst | 176 | 1 | 176 | 1 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysidxstats.nc | 144 | 1 | 144 | 1 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysiscols.clst | 100 | 1 | 100 | 1 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysiscols.nc1 | 88 | 1 | 88 | 1 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysobjvalues.clst | 596 | 5 | 596 | 5 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysrowsets.clust | 132 | 1 | 132 | 1 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysrscols.clst | 528 | 4 | 528 | 4 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.clst | 1040 | 6 | 1276 | 6 | 236 | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1 | 820 | 6 | 1060 | 6 | 240 | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2 | 820 | 6 | 1060 | 6 | 240 | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc3 | 480 | 6 | 480 | 6 | | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.syssingleobjrefs.clst | 96 | 1 | | | -96 | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.syssingleobjrefs.nc1 | 88 | 1 | | | -88 | | LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | Unknown Alloc Unit | 72092 | 19 | 72092 | 19 | | | LOP_DELETE_ROWS | LCX_TEXT_MIX | Unknown Alloc Unit | 16348 | 37 | 16348 | 37 | | | LOP_FORMAT_PAGE | LCX_HEAP | Unknown Alloc Unit | 1596 | 19 | 1596 | 19 | | | LOP_FORMAT_PAGE | LCX_IAM | Unknown Alloc Unit | 252 | 3 | 252 | 3 | | | LOP_FORMAT_PAGE | LCX_INDEX_INTERIOR | Unknown Alloc Unit | 84 | 1 | 84 | 1 | | | LOP_FORMAT_PAGE | LCX_TEXT_MIX | Unknown Alloc Unit | 4788 | 57 | 4788 | 57 | | | LOP_HOBT_DDL | LCX_NULL | | 108 | 3 | 108 | 3 | | | LOP_HOBT_DELTA | LCX_NULL | | 9600 | 150 | 9600 | 150 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysallocunits.clust | 456 | 3 | 456 | 3 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.syscolpars.clst | 644 | 4 | 644 | 4 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysidxstats.clst | 180 | 1 | 180 | 1 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysiscols.clst | 104 | 1 | 104 | 1 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysobjvalues.clst | 616 | 5 | 616 | 5 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysrowsets.clust | 136 | 1 | 136 | 1 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysrscols.clst | 544 | 4 | 544 | 4 | | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.sysschobjs.clst | 1064 | 6 | 1300 | 6 | 236 | | LOP_INSERT_ROWS | LCX_CLUSTERED | sys.syssingleobjrefs.clst | 100 | 1 | | | -100 | | LOP_INSERT_ROWS | LCX_CLUSTERED | Unknown Alloc Unit | 135888 | 19 | 135888 | 19 | | | LOP_INSERT_ROWS | LCX_INDEX_INTERIOR | Unknown Alloc Unit | 1596 | 19 | 1596 | 19 | | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysallocunits.nc | 288 | 3 | 288 | 3 | | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.syscolpars.nc | 500 | 4 | 500 | 4 | | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysidxstats.nc | 148 | 1 | 148 | 1 | | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysiscols.nc1 | 92 | 1 | 92 | 1 | | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc1 | 844 | 6 | 1084 | 6 | 240 | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc2 | 844 | 6 | 1084 | 6 | 240 | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.sysschobjs.nc3 | 504 | 6 | 504 | 6 | | | LOP_INSERT_ROWS | LCX_INDEX_LEAF | sys.syssingleobjrefs.nc1 | 92 | 1 | | | -92 | | LOP_INSERT_ROWS | LCX_TEXT_MIX | Unknown Alloc Unit | 5112 | 71 | 5112 | 71 | | | LOP_MARK_SAVEPOINT | LCX_NULL | | 508 | 8 | 508 | 8 | | | LOP_MODIFY_COLUMNS | LCX_CLUSTERED | Unknown Alloc Unit | 1560 | 10 | 1560 | 10 | | | LOP_MODIFY_HEADER | LCX_HEAP | Unknown Alloc Unit | 3780 | 45 | 3780 | 45 | | | LOP_MODIFY_ROW | LCX_CLUSTERED | sys.syscolpars.clst | 384 | 4 | 384 | 4 | | | LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysidxstats.clst | 100 | 1 | 100 | 1 | | | LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysrowsets.clust | 92 | 1 | 92 | 1 | | | LOP_MODIFY_ROW | LCX_CLUSTERED | sys.sysschobjs.clst | 1144 | 13 | 1144 | 13 | | | LOP_MODIFY_ROW | LCX_IAM | Unknown Alloc Unit | 4224 | 48 | 4224 | 48 | | | LOP_MODIFY_ROW | LCX_PFS | Unknown Alloc Unit | 13632 | 169 | 13632 | 169 | | | LOP_MODIFY_ROW | LCX_TEXT_MIX | Unknown Alloc Unit | 108640 | 120 | 108640 | 120 | | | LOP_ROOT_CHANGE | LCX_CLUSTERED | sys.sysallocunits.clust | 960 | 10 | 960 | 10 | | | LOP_SET_BITS | LCX_GAM | Unknown Alloc Unit | 1200 | 20 | 1200 | 20 | | | LOP_SET_BITS | LCX_IAM | Unknown Alloc Unit | 1080 | 18 | 1080 | 18 | | | LOP_SET_BITS | LCX_SGAM | Unknown Alloc Unit | 120 | 2 | 120 | 2 | | | LOP_SHRINK_NOOP | LCX_NULL | | | | 32 | 1 | 32 | +-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+ | Total | | | 410144 | 1095 | 411232 | 1092 | 1088 | +-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+ 

在哪种情况下,一个performance优于另一个?

对于较小的表(less于1000行)使用临时variables,否则使用临时表。

@ wcm – 实际上nit挑选表variables不是Ram只 – 它可以部分存储在磁盘上。

临时表可以有索引,而表variables只能有主索引。 如果速度是一个问题,表variables可以更快,但显然如果有很多logging,或者需要search聚集索引的临时表,那么临时表会更好。

好的背景文章

  1. 临时表:临时表很容易创build和备份数据。

    表variables:但是表variables涉及通常创build正常表时的努力。

  2. 临时表:临时表结果可以被多个用户使用。

    表variables:但是表variables只能被当前用户使用。

  3. 临时表:临时表将被存储在tempdb中。 它会使networkingstream量。 当我们在临时表中有大量数据时,它必须在数据库上工作。 性能问题将存在。

    表variables:但是一个表variables将存储在物理内存中的一些数据,然后当大小增加后,它将被移动到tempdb。

  4. 临时表:临时表可以完成所有的DDL操作。 它允许创build索引,丢弃,修改等。

    表variables:表variables不允许执行DDL操作。 但是tablevariables只允许我们创build聚集索引。

  5. 临时表:临时表可用于当前会话或全局。 这样一个多用户会话可以利用表中的结果。

    表variables:但是表variables可以用于该程序。 (存储过程)

  6. 临时表:临时variables不能使用事务。 当我们使用临时表执行DML操作时,可以回滚或提交事务。

    表variables:但是我们不能做表variables。

  7. 临时表:函数不能使用临时variables。 更多的是,我们不能在function上做DML操作。

    表variables:但是该函数允许我们使用表variables。 但是使用表variables我们可以做到这一点。

  8. 临时表:当我们对每个后续调用使用临时variables时,存储过程将执行重新编译(不能使用相同的执行计划)。

    表variables:而表variables不会这样做。

对于所有相信温度variables只在记忆中的神话

首先,表variables不一定是内存驻留。 在内存压力下,属于表variables的页面可以推送到tempdb。

阅读这里的文章: TempDB ::表variablesvs本地临时表

另一个主要区别在于表variables没有列统计信息,而临时表则是这样。 这意味着查询优化器不知道表variables中有多less行(它猜测1),如果表variables实际上具有大量行,则可能会导致生成非最优计划。

引用来自; 专业的SQL Server 2012内部和故障排除

统计信息临时表和表variables之间的主要区别在于统计信息不是在表variables上创build的。 这有两个主要结果,第一个是查询优化器对表variables中的行数使用固定的估计,而不考虑它所包含的数据。 而且,添加或删除数据不会改变估计。

索引虽然可以创build约束,但不能在表variables上创build索引。 这意味着通过创build主键或唯一约束,您可以为表variables创build索引(因为它们是为了支持约束而创build的)。 即使您有约束条件,并且因此具有统计信息的索引,编译查询时也不会使用这些索引,因为这些索引在编译时不会存在,也不会导致重新编译。

架构修改可以在临时表上进行架构修改,但不能在表variables上进行修改。 虽然在临时表上可以修改模式,但避免使用它们是因为它们会导致使用表的语句的重新编译。

临时表与表变量

TABLE VARIABLES不是在内存中创build的

有一个常见的误解,即表variables是内存中的结构,因此比临时表执行得更快 。 感谢一个叫做sys的DMV。 dm _ db _ session _ space _ usage,它显示了会话的tempdb使用情况, 可以certificate情况并非如此 。 重新启动SQL Server以清除DMV后,运行以下脚本以确保您的会话_id对用户_对象返回0 _ alloc _ page _ count:

 SELECT session_id, database_id, user_objects_alloc_page_count FROM sys.dm_db_session_space_usage WHERE session_id > 50 ; 

现在可以通过运行以下脚本来检查临时表使用多less空间来创build具有一列的临时表并使用一行填充它:

 CREATE TABLE #TempTable ( ID INT ) ; INSERT INTO #TempTable ( ID ) VALUES ( 1 ) ; GO SELECT session_id, database_id, user_objects_alloc_page_count FROM sys.dm_db_session_space_usage WHERE session_id > 50 ; 

在我的服务器上的结果表明该表在tempdb中分配了一个页面。 现在运行相同的脚本,但是这次使用一个表variables:

 DECLARE @TempTable TABLE ( ID INT ) ; INSERT INTO @TempTable ( ID ) VALUES ( 1 ) ; GO SELECT session_id, database_id, user_objects_alloc_page_count FROM sys.dm_db_session_space_usage WHERE session_id > 50 ; 

哪一个使用?

不pipe你是否使用临时表或表variables,都应该通过彻底的testing来决定,但是最好将临时 表作为默认值,因为出现 错误的可能性 要less得多

我见过客户使用表variables来开发代码,因为他们处理的是less量的行,而且比临时表快,但几年之后,表variables中有成千上万行,性能很差,所以当你做出决定的时候,请尝试一下容量计划!

另一个区别:

表var只能从创build它的过程中的语句访问,而不能从该过程调用的其他过程或嵌套的dynamicSQL(通过exec或sp_executesql)访问。

另一方面,临时表的作用域包括调用过程中的代码和嵌套的dynamicSQL。

如果您的过程创build的表必须可以从其他调用的过程或dynamicSQL访问,则必须使用临时表。 这在复杂的情况下可以非常方便。

另外考虑一下,你可以经常用派生表来replace它们,这也可能更快。 与所有性能调优一样,只有对实际数据的实际testing才能告诉您针对您的特定查询的最佳方法。

临时表

临时表的行为像一个真正的表,但在运行时创build。 它的工作类似于真正的桌子。 我们几乎可以将所有可能的操作都变成真正的表格。 我们可以在临时表中使用DDL语句,如ALTER,CREATE,DROP。

临时表结构的任何改变在创build后都是可能的。 临时表存储在“tempdb”系统数据库的数据库中。

临时表参与交易,logging或locking。 由于这个原因,它比表variables慢。

表variables

它是可变的,但像一张桌子一样工作。 它也被创build到不在内存中的Tempdb数据库中。 表variables仅在批处理或存储过程范围中可用。 您不需要删除表variables,当批处理和存储过程执行过程完成时它会自动删除

表variables支持主键,创build时的身份。 但是它不支持非聚集索引。 声明主键后,身份不能修改。

表variables不参与事务,日志logging或locking。 事务,日志logging和locking不影响表variables。

请阅读这篇文章 – http://goo.gl/GXtXqz