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

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 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
目录
相关文章
|
2月前
|
关系型数据库 MySQL 网络安全
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
|
4月前
|
SQL 存储 监控
SQL Server的并行实施如何优化?
【7月更文挑战第23天】SQL Server的并行实施如何优化?
111 13
|
4月前
|
SQL
解锁 SQL Server 2022的时间序列数据功能
【7月更文挑战第14天】要解锁SQL Server 2022的时间序列数据功能,可使用`generate_series`函数生成整数序列,例如:`SELECT value FROM generate_series(1, 10)。此外,`date_bucket`函数能按指定间隔(如周)对日期时间值分组,这些工具结合窗口函数和其他时间日期函数,能高效处理和分析时间序列数据。更多信息请参考官方文档和技术资料。
|
4月前
|
SQL 存储 网络安全
关系数据库SQLserver 安装 SQL Server
【7月更文挑战第26天】
60 6
|
4月前
|
SQL 运维 分布式计算
DataWorks产品使用合集之ODPPS中如何使用SQL查询从表中获取值并将其赋值给临时变量以供后续使用
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
4月前
|
存储 SQL C++
对比 SQL Server中的VARCHAR(max) 与VARCHAR(n) 数据类型
【7月更文挑战7天】SQL Server 中的 VARCHAR(max) vs VARCHAR(n): - VARCHAR(n) 存储最多 n 个字符(1-8000),适合短文本。 - VARCHAR(max) 可存储约 21 亿个字符,适合大量文本。 - VARCHAR(n) 在处理小数据时性能更好,空间固定。 - VARCHAR(max) 对于大文本更合适,但可能影响性能。 - 选择取决于数据长度预期和业务需求。
371 1
|
3月前
|
SQL 安全 Java
驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client
驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The server selected protocol version TLS10 is not accepted by client
470 0
|
4月前
|
SQL Oracle 关系型数据库
MySQL、SQL Server和Oracle数据库安装部署教程
数据库的安装部署教程因不同的数据库管理系统(DBMS)而异,以下将以MySQL、SQL Server和Oracle为例,分别概述其安装部署的基本步骤。请注意,由于软件版本和操作系统的不同,具体步骤可能会有所变化。
305 3
|
4月前
|
SQL 监控 数据库
SQL Server 查询超时问题排查
【7月更文挑战第8天】排查 SQL Server 查询超时涉及五个主要方面:检查复杂查询、评估服务器性能、审视配置参数、更新统计信息和分析执行计划。关注点包括查询的结构(如连接、子查询和索引),服务器资源(CPU、内存、网络延迟),连接和内存设置,以及统计信息的时效性。通过这些步骤可定位并解决性能瓶颈。
119 0
|
4月前
|
SQL 存储 Oracle
TDengine 3.3.2.0 发布:新增 UDT 及 Oracle、SQL Server 数据接入
**TDengine 3.3.2.0 发布摘要** - 开源与企业版均强化性能,提升WebSocket、stmt模式写入与查询效率,解决死锁,增强列显示。 - taos-explorer支持geometry和varbinary类型。 - 企业版引入UDT,允许自定义数据转换。 - 新增Oracle和SQL Server数据接入。 - 数据同步优化,支持压缩,提升元数据同步速度,错误信息细化,支持表名修改。 - 扩展跨平台支持,包括麒麟、Euler、Anolis OS等。
125 0