IF OBJECT_ID('tempdb..#tb_table','U') IS NOT NULL
BEGIN
TRUNCATE TABLE #tb_table
DROP TABLE #tb_table
我时常在一些图书和网络上的文章看到很多人都会说临时表是存储在磁盘上,而表变量是存储在内存中。这种说法太过武断,并且是错误的。这一节,我们会从两个方面来讨论临时表和表变量的存储方式,通过这一节,你就知道这个观点错在那里了。
结构定义信息
数据存储
结构定义信息
结构定义信息是指创建临时表或表变量结构的定义信息,比如:约束、索引、表结构等。到底这些定义信息存放在哪里?或者说我们如何获取临时表和表变量的结构定义信息呢?在临时表创建完毕后,我们使用下面的语句可以查看:
USE tempdb
;WITH DATA
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
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
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
-- If exists named temp table, drop it.
IF OBJECT_ID('tempdb..#tb_dataLocation','U') IS NOT NULL
DROP TABLE #tb_dataLocation
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;
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
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;
让我们看看第一个新开的连接执行的结果情况,从结果来看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
-- 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
-- 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)
@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
PostgreSQL技术周刊第13期:PSQL新增变量记录SQL语句的执行情况和错误
PostgreSQL(简称PG)的开发者们:云栖社区已有5000位PG开发者,发布了3000+PG文章(文章列表),沉淀了700+的PG精品问答(问答列表)。 PostgreSQL技术周刊会为大家介绍最新的PG技术与动态、预告活动、最热问答、直播教程等,欢迎大家订阅PostgreSQL技术周刊。
PostgreSQL 11 版本新增加 ERROR、SQLSTATE、ROW_COUNT、LAST_ERROR_MESSAGE、LAST_ERROR_SQLSTATE 五个变量用来记录SQL语句的执行结果状态和错误信息。
原文:SQL Server 中VARCHAR(MAX)变量赋值引起的性能问题。
案例环境:
操作系统版本 : Windows Server 2008 R2 Standard SP1
数据库版本 : Microsoft SQL Server 2012 (SP1) - 11.0.3000.0 (X64)
案例介绍:
由于不能将生产环境的代码和数据贴上来,所以我构造了下面一个小案例,当然没法和生产环境的案例一致。