什么时候应该在SQL Server中使用表变量与临时表?


298

我正在表变量中学习更多详细信息。它说临时表始终在磁盘上,而表变量在内存中,也就是说,表变量的性能优于临时表,因为表变量使用的IO操作少于临时表。

但是有时,如果表变量中的记录太多而无法包含在内存中,则该表变量将像temp表一样放在磁盘上。

但是我不知道“太多记录”是什么。100,000条记录?或1000,000条记录?我怎么知道我正在使用的表变量是在内存中还是在磁盘上?SQL Server 2005中是否有任何功能或工具可以测量表变量的规模,或者何时将表变量从内存放入磁盘时通知我?


5
一个表变量几乎总是存在的tempDB-“内存中”是一个神话。另外:查询优化器将始终将表变量视为仅保留一行-如果您有更多行,这可能导致严重的执行计划。
marc_s 2012年

您可能会发现这是很有帮助的stackoverflow.com/questions/27894/...
伊戈尔·鲍里先科

2
@marc_s-您可以在该语句中删除“几乎”。它始终存在tempdb(但也可能完全存在)
马丁·史密斯

2
使用SQL 2014,您现在可以在内存中创建表变量
狗仔队

Answers:


362

您的问题表明您已经屈服于一些围绕表变量和临时表的常见误解。

在DBA网站上写了相当详尽的答案,着眼于两种对象类型之间的差异。这也解决了有关磁盘与内存的问题(我没有发现两者之间的行为有任何显着差异)。

关于标题中的问题,尽管何时使用表变量还是使用本地临时表,您并非总是可以选择。例如,在函数中,只能使用表变量,并且如果需要在子作用域中写入表,则仅#temp表可以使用(表值参数允许只读访问)。

您可以选择以下建议(尽管最可靠的方法是仅对您的特定工作负载进行测试)。

  1. 如果您需要无法在表变量上创建的索引,那么您当然将需要一个#temporary表。但是,其详细信息取决于版本。对于SQL Server 2012及以下版本,只能在表变量上创建的索引是通过UNIQUEor PRIMARY KEY约束隐式创建的索引。SQL Server 2014为.NET中可用选项的子集引入了内联索引语法CREATE INDEX。由于允许过滤索引条件,因此对此进行了扩展。INCLUDE但是,仍然无法在表变量上创建带有-d列的索引或列存储索引。

  2. 如果要在表中反复添加和删除大量行,请使用#temporary表。支持TRUNCATE(它比更有效的DELETE大表)以下一个和另外的后续插入TRUNCATE比那些可以具有更好的性能以下一个DELETE 如这里所示

  3. 如果要删除或更新大量行,则temp表的性能可能比表变量好得多-如果它能够使用行集共享(请参见下面的“行集共享的效果”)。
  4. 如果使用该表的最佳计划会因数据而异,则使用一个#temporary表。支持创建统计信息,该统计信息允许根据数据动态地重新编译计划(尽管对于存储过程中缓存的临时表,需要分别了解重新编译行为)。
  5. 如果使用表进行查询的最佳计划不太可能更改,则可以考虑使用表变量来跳过统计信息创建和重新编译的开销(可能需要提示来修正所需的计划)。
  6. 如果插入到表中的数据的来源来自可能昂贵的SELECT语句,那么请考虑使用表变量将阻止使用并行计划进行此操作的可能性。
  7. 如果您需要表中的数据来抵抗外部用户事务的回滚,请使用表变量。一个可能的用例是在一个较长的SQL批处理中记录不同步骤的进度。
  8. #temp用户中使用表时,事务锁的保存时间可能比表变量的保存时间长(可能直到事务结束与语句结束有关,具体取决于锁的类型和隔离级别),而且它还可以防止tempdb事务日志被截断,直到用户交易结束。因此,这可能有利于表变量的使用。
  9. 在存储的例程中,可以缓存表变量和临时表。缓存的表变量的元数据维护少于表的维护#temporary。鲍勃·沃德(Bob Ward)在tempdb演讲中指出,这可能导致在高并发条件下对系统表进行额外的争用。另外,当处理少量数据时,这可以对性能产生可测量的影响

行集共享的影响

DECLARE @T TABLE(id INT PRIMARY KEY, Flag BIT);

CREATE TABLE #T (id INT PRIMARY KEY, Flag BIT);

INSERT INTO @T 
output inserted.* into #T
SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY @@SPID), 0
FROM master..spt_values v1, master..spt_values v2

SET STATISTICS TIME ON

/*CPU time = 7016 ms,  elapsed time = 7860 ms.*/
UPDATE @T SET Flag=1;

/*CPU time = 6234 ms,  elapsed time = 7236 ms.*/
DELETE FROM @T

/* CPU time = 828 ms,  elapsed time = 1120 ms.*/
UPDATE #T SET Flag=1;

/*CPU time = 672 ms,  elapsed time = 980 ms.*/
DELETE FROM #T

DROP TABLE #T

2
嗨,马丁·史密斯先生。在mi情况下,我只想存储一组Ids值,以在存储过程中的其他查询中使用它们。那你推荐我什么呢?
Jeancarlo Fontalvo '16

@JeancarloFontalvo-一个具有主键id且使用的表变量OPTION (RECOMPILE)可能对此很合适-但请同时进行测试。
马丁·史密斯

临时表和表变量的元数据争用是否相同?
Syed Aqeel Ashiq

@赛义德 通常电视较少。如果在用户事务内部,则可以更早释放锁。另请参见Bob Ward链接。
马丁·史密斯

73

如果数据量非常小(千字节),请使用表变量

使用临时表存储大量数据

另一种思考的方式:如果您认为自己可以从索引,自动统计信息或任何SQL优化器优点中受益,则对于表变量而言,数据集可能太大。

在我的示例中,我只想将大约20行设置为一种格式,然后将它们作为一个组进行修改,然后再使用它们来更新/插入永久表。因此,表变量是完美的。

但是我也运行SQL一次回填数千行,我可以肯定地说临时表性能比表变量好得多。

这与出于类似大小原因而关注CTE的方式没有什么不同-如果CTE中的数据非常小,我发现CTE的性能与优化器所提供的性能一样好或更好,但是如果它很大,则对你很不好

我的理解主要基于http://www.developerfusion.com/article/84397/table-variables-v-temporary-tables-in-sql-server/,其中有很多详细信息。


外卖的是,表变量适用于小型数据集,但对于大型数据集则使用临时表。我有数千行的查询。通过从表变量切换到临时表,查询时间从40s减少到5s,其他所有条件都相同。
liang

42

微软在这里说

表变量没有分布统计信息,它们将不会触发重新编译。因此,在许多情况下,优化器将在表变量没有行的假设下构建查询计划。因此,如果期望更多的行(大于100),则应谨慎使用表变量。在这种情况下,临时表可能是更好的解决方案。


14

我完全同意Abacus(对不起-没有足够的意见要发表)。

另外,请记住,记录不一定取决于您有多少记录,而是记录的大小

例如,您是否考虑了每条50列的1,000条记录与每条仅5列的100,000条记录之间的性能差异?

最后,也许您正在查询/存储比所需更多的数据?这是有关SQL优化策略的好书。限制要提取的数据量,特别是如果您不使用所有数据时(某些SQL程序员确实很懒,即使他们只使用很小的子集,他们也会选择所有内容)。不要忘记SQL查询分析器也可能成为您最好的朋友。


4

变量表仅适用于当前会话,例如,如果您需要当前会​​话中的EXEC另一个存储过程,则必须传递该表,因为Table Valued Parameter这当然会影响性能,使用临时表,您只能使用传递临时表名

要测试临时表:

  • Open Management Studio查询编辑器
  • 创建一个临时表
  • 打开另一个查询编辑器窗口
  • 从此表中选择“可用”

要测试变量表:

  • Open Management Studio查询编辑器
  • 创建一个变量表
  • 打开另一个查询编辑器窗口
  • 从该表中选择“不可用”

我还经历了一些其他事情:如果您的架构没有GRANT创建表的权限,请使用变量表。


3

在声明的表中写入数据declare @tb并与其他表连接后,我意识到与临时表相比,响应时间tempdb .. # tb要高得多。

当我使用@tb将它们加入时,返回结果的时间要长得多,与#tm不同,返回几乎是瞬时的。

我用10,000行联接进行了测试,并与其他5个表联接


您能否发布为获得这些数字而进行的测试?
丹德夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.