SQL Server 临时表和表变量系列之认知误区篇

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
简介: # 摘要 关于临时表和表变量,是一个老生常谈的话题,但是,我相信很多SQL Server老司机都存在或多或少的认知误区。指出一些常见的认知误区就是写作本文的目的,希望以此来找到一些常常被我们忽略的地方。 # 认知误区 SQL Server关于临时表和表变量的常见的认知误区包含以下六点: 表变量不支持事务 表变量不能创建索引 表变量没有统计信息 表变量存驻留在内存中 表变量

摘要

关于临时表和表变量,是一个老生常谈的话题,但是,我相信很多SQL Server老司机都存在或多或少的认知误区。指出一些常见的认知误区就是写作本文的目的,希望以此来找到一些常常被我们忽略的地方。

认知误区

SQL Server关于临时表和表变量的常见的认知误区包含以下六点:
表变量不支持事务
表变量不能创建索引
表变量没有统计信息
表变量存驻留在内存中
表变量访问比临时表快
局部临时表不需要手动回收资源

表变量不支持事务

从我们前一篇文章SQL Server 临时表和变量系列之对比篇中的“对事务支持”部分的测验来看,的确表变量是不支持用户事务回滚的。但是,以此得出表变量不支持事务这种说法是错误的。原因是:表变量的确不支持用户显示事务回滚,即有BEGIN TRANSACTION类型的事务回滚,但是表变量还是支持DML语言操作的事务性的。
怎么理解呢?就是说,表变量数据操作的时候,是不可能存在一些数据成功,一些数据失败的。比如:在一个批处理中,向表变量中插入两条记录,不可能存在只有一条记录插入成功,而另一条记录插入失败的情况;只可能是两条数据都插入成功或者都不成功的情况。这就是表变量支持事务原子性的一种表现。

--Table variable support transaction
-- declare temp variable
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() UNION ALL SELECT NEWID();

--END

表变量不能创建索引

表变量本质上是一种特殊的变量类型,只不过它具有了很多表的属性。比如:字段、字段数据类型、字段宽度、唯一约束、NULL、NOT NULL约束、CHECK和DEFAULT约束等。由于表变量定义完毕以后,不支持对表变量结构的任何变更和索引创建,所以很多人会认为表变量不能创建索引,比如这篇文章SQL Server Temp Table vs Table Variable Performance Testing中的这句话“However, when we query rows using the indexed column of the temporary table, which is not indexed in the table variable since this is not available for table variables”就认为表变量无法创建索引,这个观点的错误,导致了整个测试结论是错误的。其实,表变量完全可以在定义的时候就直接创建索引,参见如下代码,在定义表变量时候创建了非聚集索引IX_Indate:

--Table variable support Index
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)
);
--END

表变量没有统计信息

在关系型数据库RDBMS系统中,统计信息是执行计划正确评估的基础,和SQL执行效率息息相关,对SQL语句的查询性能起着至关重要的作用。关于SQL Server统计信息的详情,请参见之前的文章SQL Server幕后英雄 - 统计信息。那么,表变量到底有没有统计信息呢?在过往的经验中,很多SQL Server数据库的同行都认为表变量没有统计信息存在,其实这种认识是错误的。我们可以参加下面的例子:

--Table variable has statistics
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
--END

执行完毕后,表变量的统计信息展示如下:
01.png

表变量仅驻留在内存中

我在一些图书或者网络上的文章经常看到有人讲,表变量的数据是驻留在内存中的。这个观点在大部分情况下是正确的,但是当SQL Server认为数据库内存不足时(不是物理机内存不足,是SQL OS的内存不足),表变量的数据是会写入到Tempdb的数据文件,即写入了磁盘文件中。
我们可以参照下面的思路来证明SQL Server内存不足时,表变量的数据会写入tempdb数据文件:
修改Max Server Memory为512MB
重启SQL Service使得配置生效
获取测试前的内存使用量和Tempdb数据文件大小
向表变量中存入10000条记录(大概会占据780MB空间,超过512MB的最大内存大小)
再次获取测试后的内存使用量和Tempdb数据文件大小
代码如下:

USE tempdb
GO

--table variable just saved data into memory

-- change configuration of Max Server Memory to 512MB.
EXEC sys.sp_configure 'show advanced options', 1
GO
RECONFIGURE WITH OVERRIDE
GO
EXEC sys.sp_configure 'max server memory (MB)', 512
GO
RECONFIGURE WITH OVERRIDE
GO

-- restart SQL Server serivces

-- if you didn't restart sql service, clean the buffer pool
CHECKPOINT
GO
DBCC DROPCLEANBUFFERS

-- Check memory used before testing
SELECT MemoryUsedBefore = count(1) / 128.
FROM sys.dm_os_buffer_descriptors b 
WHERE is_modified = 1

-- Check disk space used before testing
SELECT
        [DBName] = db_name()
        , [LogName] = s.name
        , [SpaceUsedBefore] = CAST(FILEPROPERTY(s.name, 'SpaceUsed') AS float)* CONVERT(float,8)/1024
FROM sys.database_files AS s

-- table variable data inserting area
DECLARE  @saveToDisk TABLE(bigChar char(8000));
DECLARE
    @do int = 1; 
WHILE @do <= 100000
BEGIN
    INSERT INTO @saveToDisk VALUES(REPLICATE('A', 8000));
    SET @do = @do + 1;
END

-- Check memory used after testing
SELECT MemoryUsedAfter = count(1) / 128.
FROM sys.dm_os_buffer_descriptors b 
WHERE is_modified = 1

-- Check disk space used after testing
SELECT
        [DBName] = db_name()
        , [LogName] = s.name
        , [SpaceUsedAfter] = CAST(FILEPROPERTY(s.name, 'SpaceUsed') AS float)* CONVERT(float,8)/1024
FROM sys.database_files AS s

执行结果截图如下:
02.png

分析结果:
从结果来看Tempdb数据文件从2.4MB增长到783.7MB,大约增加了780MB,这个数据增长量和表变量需要暂存的数据量大小是非常相近的;内存大小从0.3MB增长到250.6MB,大约增长了250MB,接近SQL Server Max Server Memory的一半。这个现象已经可以说明当SQL Server最大内存大小不足以存放表变量的数据大小的时候,SQL Server会使用tempdb数据文件来暂存表变量的数据;还可以推测,当表变量存放数据接近SQL Server Max Server Memory一半空间的时候,就会将表变量数据写入磁盘。
从Windows资源管理器层面我们也可以发现sqlservr进程有大量写入Tempdb数据文件的操作。如下截图,在执行表变量插入操作的时候,Disk Write速度接近了100MB/Sec,平时的速度不到100KB/Sec:
03.png

表变量访问比临时表快

由之前关于临时表和表变量系列文章SQL Server 临时表和变量系列之对比篇中“数据存储”部分结论,我们知道表变量的数据是存放在SQL Server的内存缓存中,而临时表是存放在Tempdb数据文件的磁盘上,因此很多人会得出结论,使用表变量比临时表效率更高,性能更好。这种认知也是不够全面和错误的,理由是表变量数据也有可能存在磁盘中:
本文的前一小节“表变量仅驻留在内存中”,我们已经使用实际例子证明了,当SQL Server内存大小不足以存放表变量数据大小的时候,SQL Server会将表变量的数据全部存放在Tempdb的数据文件磁盘上,以此来避免因为内存不足而导致服务死掉。换句话说,当这种情况出现的时候(当然这种场景在现在大内存数据库时代已经非常少见了),表变量和临时表都是将数据暂存在磁盘上,数据存取都必须有物理磁盘的读写操作,无所谓谁快谁慢。基于大多数人习惯于不给表变量创建索引,这种场景反而会导致表变量的查询比临时表慢,性能更低。

局部临时表不需要手动回收资源

由之前关于临时表和表变量系列文章SQL Server 临时表和变量系列之对比篇中“析构方式”部分,我们知道局部临时表在当前进程退出的时候,SQL Server系统会自动析构局部临时表对象。因此,很多人认为我们没必要再手动回收临时表资源。
但是,我个人还是强烈建议手动回收临时表资源,又特别是临时表中暂存了大量数据的时候(比如超过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

写在最后

这篇文章主要谈论了人们常见的关于临时表和表变量的认知误区,希望能带给大家关于两者的正确认识,也为下一篇文章“SQL Server 临时表和表变量系列之选择篇”打下基础。

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
目录
相关文章
|
7天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
46 10
|
1月前
|
SQL 数据库 数据安全/隐私保护
Sql Server数据库Sa密码如何修改
Sql Server数据库Sa密码如何修改
|
17天前
|
SQL
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
启动mysq异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案
15 0
|
7天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
63 6
|
7天前
|
SQL 存储 数据挖掘
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
服务器数据恢复环境: 一台安装windows server操作系统的服务器。一组由8块硬盘组建的RAID5,划分LUN供这台服务器使用。 在windows服务器内装有SqlServer数据库。存储空间LUN划分了两个逻辑分区。 服务器故障&初检: 由于未知原因,Sql Server数据库文件丢失,丢失数据涉及到3个库,表的数量有3000左右。数据库文件丢失原因还没有查清楚,也不能确定数据存储位置。 数据库文件丢失后服务器仍处于开机状态,所幸没有大量数据写入。 将raid5中所有磁盘编号后取出,经过硬件工程师检测,没有发现明显的硬件故障。以只读方式将所有磁盘进行扇区级的全盘镜像,镜像完成后将所
数据库数据恢复—RAID5上层Sql Server数据库数据恢复案例
|
11天前
|
SQL 安全 Java
SQL server 2017安装教程
SQL server 2017安装教程
14 1
|
24天前
|
SQL 存储 Python
Microsoft SQL Server 编写汉字转拼音函数
Microsoft SQL Server 编写汉字转拼音函数
|
1月前
|
SQL 存储 数据库
数据安全无忧,SQL Server 2014数据库定时备份解密
数据安全无忧,SQL Server 2014数据库定时备份解密
|
1月前
|
SQL 网络协议 Windows
破解SQL Server迷局,彻底解决“管道的另一端无任何进程错误233”
破解SQL Server迷局,彻底解决“管道的另一端无任何进程错误233”
|
1月前
|
SQL 数据库 数据安全/隐私保护
SQL Server的安装步骤_kaic
SQL Server的安装步骤_kaic