在SQL Server中,临时表和表变量有什么区别?

425
在SQL Server 2005中,我们可以以两种不同的方式创建相似的表。
我们可以使用表变量:
declare @tmp table (Col1 int, Col2 int);

或者我们可以使用一个临时表:
create table #tmp (Col1 int, Col2 int);

这两者之间有什么区别?我看到了关于@tmp是否仍然使用tempdb,或者一切是否在内存中进行的不同观点。
在哪些情况下,一个比另一个表现更好?

15
请看我在这里的回答。 - Martin Smith
4
这里有一篇非常好的文章,作者是 Pinal Dave,链接在这里:http://blog.sqlauthority.com/2009/12/15/sql-server-difference-temptable-and-table-variable-temptable-in-memory-a-myth/。 - sam yi
13个回答

421

临时表(#tmp)和表变量(@tmp)之间存在一些差异,虽然使用tempdb不是其中之一,如下面MSDN链接所述。

作为一个经验法则,在处理小到中等数据量和简单应用场景时,您应该使用表变量。(这是一个过于宽泛的指导原则,当然有很多例外情况 - 请参见下面和后续文章。)

在选择它们之间时,需要考虑以下几点:

  • 临时表是真正的表,因此可以执行诸如CREATE INDEX等操作。如果您有大量数据需要通过索引访问,则使用临时表是一个不错的选择。

  • 通过使用PRIMARY KEY或UNIQUE约束,表变量可以拥有索引。(如果您想要一个非唯一的索引,只需将主键列包含在唯一约束的最后一列即可。如果您没有唯一的列,可以使用identity列。)SQL 2014也支持非唯一索引

  • 表变量不参与事务,并且SELECT语句隐含地使用NOLOCK。事务行为可能非常有帮助,例如如果您想在过程中回滚,那么在该事务中填充的表变量仍将被填充!

  • 临时表可能会导致存储过程重新编译,可能会很频繁。表变量不会。

  • 您可以使用SELECT INTO创建临时表,这样可以更快地编写代码(适用于即席查询),并且可能允许您处理随时间变化的数据类型,因为您不需要预先定义您的临时表结构。

  • 您可以从函数中返回表变量,使封装和重用逻辑变得更加容易(例如,创建一个函数,将字符串拆分为基于某个任意分隔符的值的表)。

  • 在用户定义的函数中使用表变量可以让这些函数更广泛地使用(详见CREATE FUNCTION文档)。如果你正在编写一个函数,除非有强制性的需求,否则应该使用表变量而不是临时表。

  • 表变量和临时表都存储在tempdb中。但是表变量(自2005年以来)默认为当前数据库的排序规则,而临时表则采用tempdb的默认排序规则(参见此处)。这意味着,如果使用临时表并且您的数据库排序规则与tempdb不同,则会出现排序问题,如果您想将临时表中的数据与数据库中的数据进行比较,则会出现问题。

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

  • 更多阅读材料:


    28
    表变量可以拥有索引。只需创建唯一约束条件,即可自动获取索引。这将极大地提高性能。(如果不需要唯一索引,请在想要的字段末尾添加实际的主键。如果没有主键,请创建一个自增列)。 - Ben
    7
    SQL Server 2014允许在表变量上指定非唯一索引。 - Martin Smith
    5
    表变量在事务中不受影响有时非常方便。如果您想在回滚后保留任何内容,可以将其放入表变量中。 - quillbreaker
    3
    为临时表创建统计信息可以提高查询计划的效率,但不能为表变量创建。这些统计信息会在临时表被删除后与其页面一起缓存一段时间,如果重新激活了缓存的表,则可能不准确。 - Michael Green
    表变量将默认为用户定义数据类型的排序规则(如果列是用户定义数据类型)或当前数据库的排序规则,而不是tempdb的默认排序规则。临时表将使用tempdb默认的排序规则。请参阅:https://technet.microsoft.com/zh-cn/library/ms188927.aspx - PseudoToad
    显示剩余3条评论

    27

    仅查看接受答案中的声明,即表变量不参与日志记录。似乎普遍不真实,至少对于表本身的insert/update/delete操作而言,在这方面我已经后来发现有一些小区别,因为存储过程中缓存的临时对象需要进行额外的系统表更新。

    我分别观察了针对以下操作的日志记录行为:@table_variable#temp表。

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

    所有操作的事务日志记录几乎相同。

    表变量版本实际上有一些额外的日志条目,因为它会向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             |
    +-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
    

    1
    纯属好奇(并为了有点迂腐),问题已经很老了(2008年8月),所以它是关于SQL 2005的。现在我们已经到了2011年底,最新的SQL是2008 R2加上Denali beta版本。你用的是哪个版本? - xanatos
    2
    @xanatos - 2008年。在2005年,表变量实际上处于劣势,因为INSERT ... SELECT没有最小化记录,并且您无法将数据插入到表变量中。 - Martin Smith
    1
    谢谢@MartinSmith,我更新了我的答案,删除了关于日志记录的声明。 - Rory

    20

    在哪些场景下,一个比另一个表现更好?

    对于较小的表(少于1000行),使用临时变量;否则使用临时表。


    18
    有支持数据吗?仅凭这样的断言并不是非常有帮助。 - Michael Myers
    9
    微软建议限制每个查询结果的行数不要超过100行:http://msdn.microsoft.com/zh-cn/library/ms175010.aspx(请参见“最佳实践”部分)。 - Artemix
    请查看我下面的答案以获取解释。 - Weihui Guo
    这在2012年是正确的,但在2021年,我认为阈值更像是100,000行或更少使用表变量,超过这个数量,使用临时表(带有索引)。 - Geoff Griswald

    18

    @wcm - 实际上,挑剔一下表变量并不仅仅是Ram内存存储 - 它可能部分存储在磁盘上。

    临时表可以有索引,而表变量只能有主索引。如果速度是一个问题,表变量可能更快,但是如果有大量记录或需要搜索具有聚集索引的临时表,则临时表会更好。

    良好的背景文章


    2
    好的背景文章 +1。我会删除我的回答,因为修改它不会留下太多东西,而且已经有很多好的答案了。 - wcm

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

      表变量:通常需要创建普通表时,表变量会增加工作量。

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

      表变量:但是只有当前用户可以使用表变量。

    3. 临时表:临时表将存储在tempdb中,这将产生网络流量。当我们在临时表中有大量数据时,它必须跨数据库工作。性能问题将存在。

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

    4. 临时表:临时表可以执行所有DDL操作。它允许创建索引、删除、更改等操作。

      表变量:而表变量不允许执行DDL操作。但是表变量允许我们仅创建聚集索引。

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

      表变量:但是表变量只能用于该程序。(存储过程)

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

      表变量:但是对于表变量,我们无法这样做。

    7. 临时表:函数不能使用临时变量。此外,我们不能在函数中执行DML操作。

      表变量:但是函数允许我们使用表变量。但是使用表变量可以实现该操作。

    8. 临时表:当我们在每个后续调用中使用临时变量时,存储过程将重新编译(无法使用相同的执行计划)。

      表变量:而表变量不会这样做。


    10

    对于那些认为临时变量仅在内存中的传说,这里有话要说:

    首先,表变量不一定在内存中。在内存压力下,属于表变量的页面可能会被推出到tempdb。

    阅读此文章:TempDB:: 表变量 vs 本地临时表


    3
    你能否将你的回答编辑成一个单一的答案,针对这两个要点进行回答?(意思为:将之前分开回答的内容合并成一个回答) - Joshua Drake

    10

    引用自:《专业 SQL Server 2012 内部原理与故障排除》

    统计信息 临时表和表变量之间的主要区别在于,表变量上不会创建统计信息。这有两个主要后果,其中第一个是查询优化器使用固定的估计值来估计表变量中行数的数量,而不管它包含的数据如何。此外,添加或删除数据也不会改变估计值。

    索引 你不能在表变量上创建索引,但可以创建约束。这意味着通过创建主键或唯一约束,你可以在表变量上拥有索引(因为这些是为支持约束而创建的)。即使你有约束,因此具有统计信息的索引,在编译查询时也不会使用这些索引,因为它们在编译时不存在,也不会引起重新编译。

    模式修改 可以在临时表上进行模式修改,但无法在表变量上进行。虽然可以对临时表进行模式修改,但应避免使用它们,因为它们会导致使用表的语句重新编译。

    Temporary Tables versus Table Variables

    表变量并非在内存中创建

    有一个普遍的误解,即表变量是在内存中创建的结构,因此比临时表更快。通过一个名为 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 ;
    

    使用哪种表?

    是否使用临时表或表变量应该通过彻底的测试来决定,但最好默认使用临时表,因为出错的可能性要少得多。

    我曾经看到客户开发了使用表变量的代码,因为他们处理的行数很少,比使用临时表更快,但是几年后表变量中有成千上万行数据时,性能非常糟糕,所以在做决策时请考虑一些容量规划!


    实际上,统计数据是在表变量上创建的,请参见http://stackoverflow.com/questions/42824366/sql-server-doesnt-have-the-statistics-for-the-table-variable。 - YuFeng Shen

    8
    另一个主要的区别是,表变量没有列统计信息,而临时表有。这意味着查询优化器不知道表变量中有多少行(它猜测为1),如果表变量实际上有很多行,可能会生成高度不优化的计划。

    2
    sys.partitions 中的 rows 列是为表变量维护的,因此它确实知道表中有多少行。这可以通过使用 OPTION (RECOMPILE) 来查看。但由于缺乏列统计信息,它无法估计特定列谓词。 - Martin Smith

    5
    这两者的关键区别在于临时表支持并行插入,而表变量不支持,这一点让我很惊讶居然没有人提到。你应该能够从执行计划中看到差异。此外,SQL Workshops on Channel 9的视频MSDN文档也可以帮助你了解更多。正如SQLMenace之前所回答的那样,这也解释了为什么对于较小的表应该使用表变量,否则应该使用临时表。

    5

    另一个区别:

    表变量只能从创建它的存储过程内的语句中访问,而不能从由该存储过程调用的其他存储过程或嵌套动态SQL(通过exec或sp_executesql)中访问。

    另一方面,临时表的范围包括被调用存储过程和嵌套动态SQL中的代码。

    如果你的存储过程创建的表必须从其他被调用的存储过程或动态SQL中访问,那么你必须使用临时表。在复杂情况下,这非常方便。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接