您的问题表明您已经屈服于一些围绕表变量和临时表的常见误解。
我在DBA网站上写了相当详尽的答案,着眼于两种对象类型之间的差异。这也解决了有关磁盘与内存的问题(我没有发现两者之间的行为有任何显着差异)。
关于标题中的问题,尽管何时使用表变量还是使用本地临时表,您并非总是可以选择。例如,在函数中,只能使用表变量,并且如果需要在子作用域中写入表,则仅#temp
表可以使用(表值参数允许只读访问)。
您可以选择以下建议(尽管最可靠的方法是仅对您的特定工作负载进行测试)。
如果您需要无法在表变量上创建的索引,那么您当然将需要一个#temporary
表。但是,其详细信息取决于版本。对于SQL Server 2012及以下版本,只能在表变量上创建的索引是通过UNIQUE
or PRIMARY KEY
约束隐式创建的索引。SQL Server 2014为.NET中可用选项的子集引入了内联索引语法CREATE INDEX
。由于允许过滤索引条件,因此对此进行了扩展。INCLUDE
但是,仍然无法在表变量上创建带有-d列的索引或列存储索引。
如果要在表中反复添加和删除大量行,请使用#temporary
表。支持TRUNCATE
(它比更有效的DELETE
大表)以下一个和另外的后续插入TRUNCATE
比那些可以具有更好的性能以下一个DELETE
如这里所示。
- 如果要删除或更新大量行,则temp表的性能可能比表变量好得多-如果它能够使用行集共享(请参见下面的“行集共享的效果”)。
- 如果使用该表的最佳计划会因数据而异,则使用一个
#temporary
表。支持创建统计信息,该统计信息允许根据数据动态地重新编译计划(尽管对于存储过程中缓存的临时表,需要分别了解重新编译行为)。
- 如果使用表进行查询的最佳计划不太可能更改,则可以考虑使用表变量来跳过统计信息创建和重新编译的开销(可能需要提示来修正所需的计划)。
- 如果插入到表中的数据的来源来自可能昂贵的
SELECT
语句,那么请考虑使用表变量将阻止使用并行计划进行此操作的可能性。
- 如果您需要表中的数据来抵抗外部用户事务的回滚,请使用表变量。一个可能的用例是在一个较长的SQL批处理中记录不同步骤的进度。
- 在
#temp
用户中使用表时,事务锁的保存时间可能比表变量的保存时间长(可能直到事务结束与语句结束有关,具体取决于锁的类型和隔离级别),而且它还可以防止tempdb
事务日志被截断,直到用户交易结束。因此,这可能有利于表变量的使用。
- 在存储的例程中,可以缓存表变量和临时表。缓存的表变量的元数据维护少于表的维护
#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
tempDB
-“内存中”是一个神话。另外:查询优化器将始终将表变量视为仅保留一行-如果您有更多行,这可能导致严重的执行计划。