SQL Server 临时表和变量系列之对比篇

摘要

在SQL Server代码编写过程中,经常会有需要临时“暂存”一部分数据结果集,供上下文使用,这个时候,我们有两种选择,即临时表和表变量。这篇文章从以下几个方面来对临时表和表变量进行对比:
创建和析构方式
存储方式
作用域
对事务的支持
性能影响

创建和析构方式

临时表和表变量在创建和析构方式上是完全不一样的,在这一节,我们会从以下几点来看看他们的不同。
结构定义
索引创建
DDL
析构方式

结构定义

在上一篇文章SQL Server 临时表和变量系列之概念篇中,我们已经知道了临时表的定义方式是CREATE TABLE,而表变量的定义方式是DECLARE TABLE,在此我们不再过多的累述。让我们重温代码既可:

USE tempdb
GO
-- Temp table
-- If exists named temp table, drop it.
IF OBJECT_ID('tempdb..#tb_table','U') IS NOT NULL
    DROP TABLE #tb_table
GO

-- And then create a new one
CREATE TABLE #tb_table(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
,Length DECIMAL(4,2) NOT NULL CHECK(Length>0.0)
,Windth DECIMAL(4,2) NOT NULL
,Height DECIMAL(4,2) NOT NULL CHECK(Height>0.0)
,Dimension AS (Length  Windth  Height)
,Indate DATETIME NOT NULL  CONSTRAINT DF_tbTable_Indate DEFAULT(GETDATE())
,CONSTRAINT CK_Windth CHECK(Windth>0.0)
);

-- table variable
-- we don't need to check existence, declare directly.
DECLARE
    @tb_table TABLE(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
,Length DECIMAL(4,2) NOT NULL CHECK(Length>0.0)
,Windth DECIMAL(4,2) NOT NULL CHECK(Windth>0.0)
,Height DECIMAL(4,2) NOT NULL CHECK(Height>0.0)
,Dimension AS (Length  Windth  Height)
,Indate DATETIME NOT NULL  DEFAULT(GETDATE())
,index IX_Indate NONCLUSTERED (Indate)
);

索引创建

临时表和表变量关于索引的创建方式是完全不一样的。临时表可以定义表结构之中或之后创建索引,而表变量只能在定义的过程中创建。如果在上面结构定义的语句之后,再执行下面的语句,第一个语句可以成功执行,而第二条语句会报错。

-- Create index for temptable, success
CREATE INDEX IX_ProductName
ON #tb_table(ProductName);
GO

-- Try to create index for table variable, failed
CREATE INDEX IX_ProductName
ON @tb_table(ProductName);
GO

执行结果如下:

DDL

关于临时表和表变量的DDL操作,与索引创建类似。临时表可以在定以后进行任何的DDL操作,而表变量在完成定以后,是完全禁止DDL操作的。假设我们需要为临时表加上一个新的字段ModifiedDate时间字段,同时我们也尝试为表变量添加这个字段定义。

-- Add a new column into temp table
ALTER TABLE #tb_table
ADD ModifiedDate  DATETIME NULL
GO

-- Try to add a new column to table variable
ALTER TABLE @tb_table
ADD ModifiedDate DATETIME NULL
GO

为临时表添加字段成功,而表变量报错,截图如下:

析构方式

临时表和表变量创建成功,使用完毕以后,系统需要回收这部分资源。在析构方式上也存在差异。针对表变量,SQL Server析构的方式比较简单,在表变量所在的批处理结束后,系统会自动回收资源,无需任何的人为干预。而对于临时表相对比较复杂一些,这里需要分为全局临时表和局部临时表。
全局临时表:以##打头的临时表称为全局临时表,此类型的临时表对所有进程可见,此类型的临时表生命周期是所有使用到全局临时表的连接完全断开后,临时表资源被系统自动回收;当然我们也可以手动析构临时表,方法是使用DROP TABLE语句。
局部临时表:以#打头的临时表称为局部临时表,这种类型的临时表仅当前进程可见,其他进程不可访问,生命周期是随着当前连接进程的关闭而消亡,临时表资源被系统自动回收;相同道理,我们同样可以使用DROP TABLE语句手动回收。

注意:
如果临时表(无论全局还是局部临时表)中存放有大量记录数(比如超过10万条记录数),手动回收临时表资源前,强烈建议使用TRUNCATE TABLE不记录日志的方式删除临时表所有数据,以防止临时表直接DROP操作给tempdb日志文件带来写入压力。

IF OBJECT_ID('tempdb..#tb_table','U') IS NOT NULL
BEGIN
    TRUNCATE TABLE #tb_table
    DROP TABLE #tb_table
END

存储方式

我时常在一些图书和网络上的文章看到很多人都会说临时表是存储在磁盘上,而表变量是存储在内存中。这种说法太过武断,并且是错误的。这一节,我们会从两个方面来讨论临时表和表变量的存储方式,通过这一节,你就知道这个观点错在那里了。
结构定义信息
数据存储

结构定义信息

结构定义信息是指创建临时表或表变量结构的定义信息,比如:约束、索引、表结构等。到底这些定义信息存放在哪里?或者说我们如何获取临时表和表变量的结构定义信息呢?在临时表创建完毕后,我们使用下面的语句可以查看:

USE tempdb
GO
;WITH DATA
AS(
    SELECT
        *
    FROM sys.objects
    WHERE object_id = object_id('#tb_table')
    UNION ALL
    SELECT *
    FROM sys.objects
    WHERE parent_object_id = object_id('#tb_table')
)
SELECT
    parent_object = OBJECT_NAME(parent_object_id)
    ,name
    ,type_desc
    ,create_date
FROM DATA
ORDER BY create_date DESC

从展示结果来看,临时表的定义信息是存放在Tempdb数据库下的:

这里有一个非常有趣的问题需要思考下:在创建临时表的定义语句中,我们的临时表名字明明是#tb_table,为什么这里却变成了#tb_table__000000000005呢?答案我们会在“作用域
”这一节来揭晓。
那让我们来看看如何获取表变量的定义,为了看到测试效果,我特意将当前数据修改到master数据库下,然后从Tempdb下去获取表变量的定义信息(其他数据库无法获取到):

-- table variable
-- we don't need to check existence, declare directly.
USE master
GO

DECLARE
    @tb_table TABLE(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
,Length DECIMAL(4,2) NOT NULL CHECK(Length>0.0)
,Windth DECIMAL(4,2) NOT NULL CHECK(Windth>0.0)
,Height DECIMAL(4,2) NOT NULL CHECK(Height>0.0)
,Dimension AS (Length  Windth  Height)
,Indate DATETIME NOT NULL  DEFAULT(GETDATE())
,index IX_Indate NONCLUSTERED (Indate)
);

SELECT TOP 7
    current_db = db_name(), name, parent_name = (SELECT TOP 1 name FROM tempdb.sys.all_objects WHERE object_id = A.parent_object_id)
    ,type_desc, create_date, [current_date] = getdate()
FROM tempdb.sys.all_objects AS A
ORDER BY create_date DESC

从展示的结果来看,我们成功的从Tempdb下获取到了表变量的定义信息,结果如下:

数据存储

写到这里,我们已经知道了临时表和表变量的定义信息均是放在Tempdb数据库下的。那么临时表和表变量的数据又是存放在哪里的呢?这一小节,我们要探讨这个问题。
首先我们来看看临时表中的数据的存储位置。我们在SSMS中开启一个连接,执行以下语句:

USE tempdb
GO
SELECT database_name = db_name(),name,type_desc,size = size/128.
FROM sys.database_files

WAITFOR DELAY '00:00:10'

SELECT database_name = db_name(),name,type_desc,size = size/128.
FROM sys.database_files

然后我们在5秒时间内开启另外一个连接,执行下面的语句

USE tempdb
GO
-- If exists named temp table, drop it.
IF OBJECT_ID('tempdb..#tb_dataLocation','U') IS NOT NULL
    DROP TABLE #tb_dataLocation
GO
CREATE TABLE #tb_dataLocation(bigChar char(8000));

DECLARE
    @do int = 1;
WHILE @do <= 10000
BEGIN
    INSERT INTO #tb_dataLocation VALUES(REPLICATE('A', 8000));
    SET @do = @do + 1;
END

SELECT
       OBJECT_NAME = object_name(object_id),
       reserved_size = SUM(reserved_page_count)/128.,
       used_pages = SUM(used_page_count),
       pages = SUM(CASE
                  WHEN index_id < 2 THEN in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count
                  ELSE lob_used_page_count + row_overflow_used_page_count
              END),
       row_count = SUM(CASE WHEN index_id < 2 THEN row_count ELSE 0 END)
FROM tempdb.sys.dm_db_partition_stats WITH(NOLOCK)
WHERE object_id = object_id('#tb_dataLocation', 'U')
GROUP BY object_id

从第二个连接执行结果展示来看,我们往临时表#tb_dataLocation中插入了10000条数据,总共占用了78.19MB空间。

我们再来看看第一个连接执行完毕后的结果,tempdb数据库数据文件增长了80MB,这个数字大小和临时表空间占用大小78.19非常接近了(因为我的Tempdb的Filegrowth设置为10MB,数据文件因为临时表数据的插入增长了8次)。

因此我们可以得出结论,临时表中的数据是存放在Tempdb的磁盘上数据文件中。
接下来,我们看看表变量中的数据到底是存放在哪里的?我们还是新开一个SSMS连接,执行如下语句。这段代码是统计SQL Server缓存(即内存)中BufferPool空间变化情况。

use master
GO
DBCC DROPCLEANBUFFERS

select total_cached_pages = count(1) / 128.
from sys.dm_os_buffer_descriptors b
where b.database_id = db_id('tempdb')
and is_modified=1

WAITFOR DELAY '00:00:10'

select total_cached_pages = count(1) / 128.
from sys.dm_os_buffer_descriptors b
where b.database_id = db_id('tempdb')
and is_modified=1

在5秒之内新开另外一个连接,执行下面的语句,表结构和临时表一致,插入的数据记录数也一致。所以,表变量中存放的数据大小也大概在78MB左右:

-- table variables
DECLARE  @tb_dataLocation TABLE(bigChar char(8000));

DECLARE
    @do int = 1;
WHILE @do <= 10000
BEGIN
    INSERT INTO @tb_dataLocation VALUES(REPLICATE('A', 8000));
    SET @do = @do + 1;
END
GO

让我们看看第一个新开的连接执行的结果情况,从结果来看SQL Server数据缓存增加了78.65减去0.55,约等于78MB左右的内存空间占用,这个大小和表变量数据大小几乎一致。

因此,我们可以得出结论,表变量数据是存放在SQL Server的缓存中,即内存中(当然也会有例外情况,当SQL Server内存空间不足时,表变量数据会写入磁盘)。

作用域

临时表和表变量的另一个不同是作用域不同。

局部临时表

局部临时表是以#打头的临时表,局部临时表仅当前进程可见,其他进程不可访问,生命周期会随着当前连接进程的关闭而消亡。那么,局部临时表是如何做到仅当前会话可见呢?在局部临时表创建的时候,SQL Server会将局部临时表名后添加一串随机字符来作为局部临时表在系统中的唯一标识符,比如:#tb_table__000000000005(这里我以两个下划线来代替多个下划线),这样可以避免其他进程在创建相同临时表名字的时候导致的命名冲突(比如:两个进程同时执行了使用临时表的存储过程),这个也是“结构定义存储”小节问题的答案。我们怎么来确定这两者是同一个临时表呢?请使用下面的方法:我们先往临时表中插入一条记录,然后分别查询这两个表名字不一样的临时表(其实是同一个表),看看数据是否一样即可。

INSERT INTO #tb_table(ProductName, Length, Windth, Height) VALUES(NEWID(), 0.1, 0.2, 0.3);
SELECT *
FROM #tb_table

SELECT *
FROM [#tb_table___________________________________________________________________________________________________________000000000005] WITH(NOLOCK)

结果展示如下,我们可以看到这两条记录是一模一样的,所以这两个表是指同一个对象。

全局临时表

以##打头的临时表称为全局,此类型的临时表对所有进程可见,当前进程和其他进程均可访问,生命周期是所有使用到全局临时表的连接完全关闭后,临时表消亡。关于全局临时表作用域的实例演示我们已经在文章SQL Server 表变量和临时表系列之概念篇中“临时表的使用”章节涉及到了,在此不再累述。

表变量

表变量的作用域和变量作用域是一致的,都是当前批处理,因为从根本上来讲表变量就是一种特殊的本地变量。在当前连接进程关闭以后,表变量会被SQL Server数据库系统自动回收而无需用户干预。

对事务支持

按照我们前面文章的分析,临时表数据是存放在tempdb物理文件的磁盘上,是一种特殊的表结构;表变量数据是基于内存存放的一种特殊变量结构。临时表对事务的支持和普通表对象保持一致;而表变量仅在更新表的时候有事务,也就是说表变量仅在操作这张表的DML操作时候支持事务,其他情况不支持事务。
让我们来看下面的例子,代码如下:

--*Transaction Support

IF OBJECT_ID('tempdb..#tb_table','U') IS NOT NULL
    DROP TABLE #tb_table
GO

-- create temptable
CREATE TABLE #tb_table(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
);

-- declare temp variable
DECLARE
    @tb_table TABLE(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
);

-- open a transaction
BEGIN TRAN

-- insert one record
INSERT INTO #tb_table
    OUTPUT INSERTED.ProductName
    INTO @tb_table(ProductName)
SELECT NEWID();

SELECT comment = 'temp table',* FROM #tb_table WITH(NOLOCK)
SELECT comment = 'table variable',* FROM @tb_table

ROLLBACK

SELECT comment = 'temp table',* FROM #tb_table WITH(NOLOCK)
SELECT comment = 'table variable',* FROM @tb_table

执行结果如下:

从这个结果截图,我们可以得出:临时表支持用户事务,表变量不支持用户事务。

性能影响

临时表和表变量均可以用作临时数据暂存媒介,具有相同的功效。但是,性能有时会有天壤之别。究其原因,我的分析是下面两个主要原因:
统计信息
动态SQL

统计信息

根据之前的经验,我们不止一次遇到过用户反馈,使用表变量的存储过程性能远远不及使用临时表的存储过程(当然其他代码保持一致的,仅将表变量替换为临时表)。按理讲,表变量存放在内存中,应该比临时表存放在磁盘读写效果高,而得出这样的结论和大多数人的认识相违背。究其原因就在于本节要讨论的主题 - 统计信息。按照一般常理,大部分人是不会对表变量创建主键、索引的,这个有可能是因为表变量只能在定义时创建主键和索引导致很多人忽略了,又或者是很多人根本没有意识到要为表变量建立索引。但是,对于临时表,大家习惯于创建主键、索引的,这就导致了表变量不存在任何的统计信息,而临时表有完整的统计信息。根据之前的文章SQL Server幕后英雄 - 统计信息我们清楚的知道统计信息会左右SQL Server的执行计划评估,和SQL执行效率息息相关,对SQL语句的查询性能起着至关重要的作用。
查看表变量的统计信息:

DECLARE
    @tb_table TABLE(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
,Length DECIMAL(4,2) NOT NULL CHECK(Length>0.0)
,Windth DECIMAL(4,2) NOT NULL CHECK(Windth>0.0)
,Height DECIMAL(4,2) NOT NULL CHECK(Height>0.0)
,Dimension AS (Length  Windth  Height)
,Indate DATETIME NOT NULL  DEFAULT(GETDATE())
,index IX_Indate NONCLUSTERED (Indate)
);

DECLARE
    @table_variable_id bigint
    ;
SELECT TOP 1 @table_variable_id = object_id
FROM tempdb.sys.all_objects AS A
WHERE parent_object_id = 0
ORDER BY create_date DESC

SELECT
    statistics_name = st.name
    ,table_name = OBJECT_NAME(st.object_id)
    ,column_name = COL_NAME(stc.object_id, stc.column_id)
FROM    sys.stats AS st WITH(NOLOCK)
        INNER JOIN sys.stats_columns AS stc WITH(NOLOCK)
            ON st.object_id = stc.object_id
            AND st.stats_id = stc.stats_id
WHERE st.object_id = @table_variable_id

执行结果如下:

动态SQL

临时表可以在调用动态SQL之前定义,在动态SQL中使用;而表变量只能在动态SQL中定义,否则会报告错误异常。如下事例:

-- ************Dynamic SQL

-- temp table
IF OBJECT_ID('tempdb..#tb_table','U') IS NOT NULL
    DROP TABLE #tb_table
GO

-- create temptable
CREATE TABLE #tb_table(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
);

INSERT INTO #tb_table SELECT NEWID();

DECLARE
    @sql nvarchar(max)
    ;

SET
    @sql = N'SELECT * FROM #tb_table WHERE RowId = @RowId'
    ;

EXEC sys.sp_executesql @sql,N'@RowID int', @RowID = 1

-- table variable
SET @sql = N'
DECLARE
    @tb_table TABLE(
RowID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,ProductName NVARCHAR(50) NOT NULL UNIQUE
);

INSERT INTO @tb_table SELECT NEWID();

SELECT * FROM @tb_table WHERE RowId = @RowId
';

EXEC sys.sp_executesql @sql,N'@RowID int', @RowID = 1
时间: 2024-08-02 23:14:06

SQL Server 临时表和变量系列之对比篇的相关文章

SQL Server 临时表和表变量系列之选择篇

摘要 通过前面的三篇系列文章,我们对临时表和表变量的概念.对比和认知误区已经有了非常全面的认识.其实,我们的终极目的,还是今天要讨论的话题,即当我们面对具体的业务场景的时候,该选择临时表还是表变量? 几种典型场景 以下是几种典型的场景,让我们看看到底该作何选择,以及做出最终选择的具体原因和考量. 存储过程嵌套 在SQL Server中,使用存储过程的好处显而易见,往往会节约存储过程执行计划编译时间,提高查询语句的执行效率.有时候,我们在构建存储过程多层次嵌套场景中,会有内层存储过程需要临时使用外

SQL Server 临时表和表变量系列之踢馆篇

摘要 在面对SQL Server选择使用临时表还是表变量作为数据暂存问题时,有一个非常重要的选择标准便是性能,两者对于查询语句和DML性能表现到底如何呢?我相信,很多人的认识是片面的,或者是错误的.这里以一篇引用率很高的文章来作为反面教材来纠正那些片面和错误的认识,我暂且称之为"踢馆". 背景 在研究临时表和表变量该如何选择的时候,一篇文章叫着SQL Server Temp Table vs Table Variable Performance Testing文章引用率是非常高的.通读

对比Oracle临时表和SQL Server临时表的不同点_oracle

Oracle数据库创建临时表的过程以及和SQL Server临时表的不同点的对比的相关知识是本文我们主要要介绍的内容,接下来就让我们一起来了解一下这部分内容吧,希望能够对您有所帮助. 1.简介 Oracle数据库除了可以保存永久表外,还可以建立临时表temporary tables.这些临时表用来保存一个会话SESSION的数据,或者保存在一个事务中需要的数据.当会话退出或者用户提交commit和回滚rollback事务的时候,临时表的数据自动清空,但是临时表的结构以及元数据还存储在用户的数据字

sql server 怎么使用变量一次性建立很多表,表的内容一样,表名不一样

问题描述 sql server 怎么使用变量一次性建立很多表,表的内容一样,表名不一样 例如 declare @i int set @i=1 while @i<=30 begin create table love+@i ( ID int, name varchar(10): ) set @i=@i+1 end 解决方案 SQL Server 如何使用命令修改表名或表的列名 和 删除表Sql server表变量sql server 存储过程中使用变量表,临时表的分析 解决方案二: 使用动态SQL

SQL Server 2008空间数据应用系列六:基于SQLCRL的空间数据可编程性

原文:SQL Server 2008空间数据应用系列六:基于SQLCRL的空间数据可编程性 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. 2.具备 Transact-SQL 编程经验和使用 SQL Server Management Studio 的经验. 3.具有使用 Microsoft Visual Studio 进行 Microsoft .NET Framework开发的经验. 4.熟悉或了解Microsoft S

SQL Server 2008空间数据应用系列三:SQL Server 2008空间数据类型

原文:SQL Server 2008空间数据应用系列三:SQL Server 2008空间数据类型 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. 2.具备 Transact-SQL 编程经验和使用 SQL Server Management Studio 的经验. 3.熟悉或了解Microsoft SQL Server 2008中的空间数据类型. 4.具备相应的GIS专业理论知识. 5.其他相关知识.     SQL S

巧用SQL server临时表

SQL server临时表是经常需要用到的,下面就教您如何使用SQL server临时表解决防止用户重复登录问题,供您参考. 在我们开发商务软件的时候,常常会遇到这样的一个问题:怎样防止用户重复登录我们的系统?特别是对于银行或是财务部门,更是要限制用户以其工号身份多次登入. 可能会有人说在用户信息表中加一字段判断用户工号登录的状态,登录后写1,退出时写0,且登录时判断其标志位是否为1,如是则不让该用户工号登录.但是这样那势必会带来新的问题:如发生象断电之类不可预知的现象,系统是非正常退出,无法将

SQL SERVER 临时表导致存储过程重编译(recompile)的一些探讨

   SQLSERVER为了确保返回正确的值,或者处于性能上的顾虑,有意不重用缓存在内存里的执行计划,而重新编译执行计划的这种行为,被称为重编译 (recompile).那么引发存储过程重编译的条件有哪一些呢?下面罗列了一些导致重编译(recompile)的条件:     - 对查询所引用的表或视图进行更改(ALTER TABLE 和 ALTER VIEW).     - 对执行计划所使用的任何索引进行更改.     - 对执行计划所使用的统计信息进行更新,这些更新可能是从语句(如 UPDATE

SQL Server 2008空间数据应用系列十一:提取MapInfo地图数据中的空间数据解决方案

原文:SQL Server 2008空间数据应用系列十一:提取MapInfo地图数据中的空间数据解决方案友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. 2.具备 Transact-SQL 编程经验和使用 SQL Server Management Studio 的经验. 3.熟悉或了解Microsoft SQL Server 2008中的空间数据类型. 4.具备相应(比如OGC规范.KML规范)的GIS专业理论知识. 5.