我们可以使用表变量:
declare @tmp table (Col1 int, Col2 int);
或者我们可以使用一个临时表:
create table #tmp (Col1 int, Col2 int);
这两者之间有什么区别?我看到了关于
@tmp
是否仍然使用tempdb,或者一切是否在内存中进行的不同观点。在哪些情况下,一个比另一个表现更好?
declare @tmp table (Col1 int, Col2 int);
create table #tmp (Col1 int, Col2 int);
@tmp
是否仍然使用tempdb,或者一切是否在内存中进行的不同观点。临时表(#tmp)和表变量(@tmp)之间存在一些差异,虽然使用tempdb不是其中之一,如下面MSDN链接所述。
作为一个经验法则,在处理小到中等数据量和简单应用场景时,您应该使用表变量。(这是一个过于宽泛的指导原则,当然有很多例外情况 - 请参见下面和后续文章。)
在选择它们之间时,需要考虑以下几点:
临时表是真正的表,因此可以执行诸如CREATE INDEX等操作。如果您有大量数据需要通过索引访问,则使用临时表是一个不错的选择。
通过使用PRIMARY KEY或UNIQUE约束,表变量可以拥有索引。(如果您想要一个非唯一的索引,只需将主键列包含在唯一约束的最后一列即可。如果您没有唯一的列,可以使用identity列。)SQL 2014也支持非唯一索引。
表变量不参与事务,并且SELECT语句隐含地使用NOLOCK。事务行为可能非常有帮助,例如如果您想在过程中回滚,那么在该事务中填充的表变量仍将被填充!
临时表可能会导致存储过程重新编译,可能会很频繁。表变量不会。
您可以使用SELECT INTO创建临时表,这样可以更快地编写代码(适用于即席查询),并且可能允许您处理随时间变化的数据类型,因为您不需要预先定义您的临时表结构。
您可以从函数中返回表变量,使封装和重用逻辑变得更加容易(例如,创建一个函数,将字符串拆分为基于某个任意分隔符的值的表)。
在用户定义的函数中使用表变量可以让这些函数更广泛地使用(详见CREATE FUNCTION文档)。如果你正在编写一个函数,除非有强制性的需求,否则应该使用表变量而不是临时表。
表变量和临时表都存储在tempdb中。但是表变量(自2005年以来)默认为当前数据库的排序规则,而临时表则采用tempdb的默认排序规则(参见此处)。这意味着,如果使用临时表并且您的数据库排序规则与tempdb不同,则会出现排序问题,如果您想将临时表中的数据与数据库中的数据进行比较,则会出现问题。
全局临时表(##tmp)是另一种对所有会话和用户可用的临时表。
更多阅读材料:
MSDN关于两者之间差异的FAQ:https://support.microsoft.com/en-gb/kb/305977
文章:https://searchsqlserver.techtarget.com/tip/Temporary-tables-in-SQL-Server-vs-table-variables
临时表和临时变量的意外行为和性能影响:Paul White on SQLblog.com
仅查看接受答案中的声明,即表变量不参与日志记录。似乎普遍不真实,至少对于表本身的insert
/update
/delete
操作而言,在这方面我已经后来发现有一些小区别,因为存储过程中缓存的临时对象需要进行额外的系统表更新。
我分别观察了针对以下操作的日志记录行为:@table_variable
和#temp
表。
所有操作的事务日志记录几乎相同。
表变量版本实际上有一些额外的日志条目,因为它会向sys.syssingleobjrefs
基表添加一个条目(稍后从中删除),但总体而言,仅因为表变量的内部名称消耗了236个字节,比#temp
表少记录了一些字节(少了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 |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
INSERT ... SELECT
没有最小化记录,并且您无法将数据插入到表变量中。 - Martin Smith在哪些场景下,一个比另一个表现更好?
对于较小的表(少于1000行),使用临时变量;否则使用临时表。
@wcm - 实际上,挑剔一下表变量并不仅仅是Ram内存存储 - 它可能部分存储在磁盘上。
临时表可以有索引,而表变量只能有主索引。如果速度是一个问题,表变量可能更快,但是如果有大量记录或需要搜索具有聚集索引的临时表,则临时表会更好。
临时表:创建和备份数据都很容易。
表变量:通常需要创建普通表时,表变量会增加工作量。
临时表:多个用户可以使用临时表的结果。
表变量:但是只有当前用户可以使用表变量。
临时表:临时表将存储在tempdb中,这将产生网络流量。当我们在临时表中有大量数据时,它必须跨数据库工作。性能问题将存在。
表变量:但是,表变量将在物理内存中存储一些数据,然后当大小增加时,它将被移动到tempdb中。
临时表:临时表可以执行所有DDL操作。它允许创建索引、删除、更改等操作。
表变量:而表变量不允许执行DDL操作。但是表变量允许我们仅创建聚集索引。
临时表:临时表可以用于当前会话或全局。这样,多个用户会话可以利用表中的结果。
表变量:但是表变量只能用于该程序。(存储过程)
临时表:临时变量不能使用事务。当我们使用临时表进行DML操作时,它可以回滚或提交事务。
表变量:但是对于表变量,我们无法这样做。
临时表:函数不能使用临时变量。此外,我们不能在函数中执行DML操作。
表变量:但是函数允许我们使用表变量。但是使用表变量可以实现该操作。
临时表:当我们在每个后续调用中使用临时变量时,存储过程将重新编译(无法使用相同的执行计划)。
表变量:而表变量不会这样做。
引用自:《专业 SQL Server 2012 内部原理与故障排除》
统计信息 临时表和表变量之间的主要区别在于,表变量上不会创建统计信息。这有两个主要后果,其中第一个是查询优化器使用固定的估计值来估计表变量中行数的数量,而不管它包含的数据如何。此外,添加或删除数据也不会改变估计值。
索引 你不能在表变量上创建索引,但可以创建约束。这意味着通过创建主键或唯一约束,你可以在表变量上拥有索引(因为这些是为支持约束而创建的)。即使你有约束,因此具有统计信息的索引,在编译查询时也不会使用这些索引,因为它们在编译时不存在,也不会引起重新编译。
模式修改 可以在临时表上进行模式修改,但无法在表变量上进行。虽然可以对临时表进行模式修改,但应避免使用它们,因为它们会导致使用表的语句重新编译。
表变量并非在内存中创建
有一个普遍的误解,即表变量是在内存中创建的结构,因此比临时表更快。通过一个名为 sys.dm_db_session_space_usage 的 DMV 可以显示会话使用 tempdb 的情况,你可以证明这不是正确的。重启 SQL Server 以清除 DMV 后,运行以下脚本来确认你的 session_id 返回 0 来指示用户对象分配页面数量:
SELECT session_id,
database_id,
user_objects_alloc_page_count
FROM sys.dm_db_session_space_usage
WHERE session_id > 50 ;
现在您可以通过运行以下脚本创建一个临时表并将其填充一行,以检查临时表使用了多少空间:
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中被分配了一页。 现在运行同样的脚本,但是这次使用表变量:
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 ;
使用哪种表?
是否使用临时表或表变量应该通过彻底的测试来决定,但最好默认使用临时表,因为出错的可能性要少得多。
我曾经看到客户开发了使用表变量的代码,因为他们处理的行数很少,比使用临时表更快,但是几年后表变量中有成千上万行数据时,性能非常糟糕,所以在做决策时请考虑一些容量规划!
sys.partitions
中的 rows
列是为表变量维护的,因此它确实知道表中有多少行。这可以通过使用 OPTION (RECOMPILE)
来查看。但由于缺乏列统计信息,它无法估计特定列谓词。 - Martin Smith另一个区别:
表变量只能从创建它的存储过程内的语句中访问,而不能从由该存储过程调用的其他存储过程或嵌套动态SQL(通过exec或sp_executesql)中访问。
另一方面,临时表的范围包括被调用存储过程和嵌套动态SQL中的代码。
如果你的存储过程创建的表必须从其他被调用的存储过程或动态SQL中访问,那么你必须使用临时表。在复杂情况下,这非常方便。