临时表与表变量深入探究

摘要:
临时表或表变量我们一般用来充当中间结果集,很多时候都在用,但真正了解他们之间的区别的人还是很少的,网上流传的说法也不甚统一,所以今天我就做一个实验,让我们看看临时表和表变量的区别,以及他们各自的用途。可以得出,表变量不受事务影响。

临时表或表变量我们一般用来充当中间结果集,很多时候都在用,但真正了解他们之间的区别的人还是很少的,网上流传的说法也不甚统一,所以今天我就做一个实验,让我们看看临时表和表变量的区别,以及他们各自的用途。

执行以下语句,对测试环境做准备

DBCC DROPCLEANBUFFERS  --从缓冲池中删除所有清除缓冲区
DBCC FREEPROCCACHE   --清除计划缓存
CHECKPOINT    --写入MDF中

1) 关于存储

表变量在内存中,是否真的不写磁盘,不会造成任何IO开销?

usetempdb  
execsp_spaceused

--database_name    database_size    unallocated space--tempdb        8.50 MB            6.75 MB

--tempdb数据库占用8.50M,未用空間6.75M

useTestDB

CREATE TABLE #Table3(id int,  AtypeId char(1024))

declare @count int = 50
INSERT INTO#Table3(id, atypeid)
SELECT TOP(@count) 1 as id, 'sss' FROM GraspFZDRPWrite001.dbo.BillType  --随便写的一张表,只是让其能循环插入50条记录

usetempdb
CHECKPOINT  --写入MDF中

execsp_spaceused

--database_name    database_size    unallocated space--tempdb        8.50 MB            6.62 MB

运行代码,我们发现,unallocated space 未用空间减小了,从6.75M减少至6.62M,说明临时表是占用了tempdb空间的,这点毋庸置疑。

我们接着看表变量又是如何?

usetempdb
execsp_spaceused

--database_name    database_size    unallocated space--tempdb        8.50 MB            6.69 MB

useTestDB

declare @Table3 table (id int,  atypeid char(1024))
declare @count int = 50

INSERT INTO @Table3(id, atypeid)
SELECT TOP(@count) 1 as id, 'sss' asatypeid
FROMGraspFZDRPWrite001.dbo.BillType 

usetempdb
checkpoint

execsp_spaceused
--database_name    database_size    unallocated space--tempdb        8.50 MB            6.62 MB

unallocated space值再次变小,说明此操作存在占用tempdb的数据库空间。两者其实都存储在tempdb中,都占用tempdb的数据库空间。

2)对表变量记录的操作是否占用更少的LOG

我们首先看临时表插入

if OBJECT_ID('tempdb..#T') is not null drop table#T

declare @b1 bigint, @b2 bigint

CREATE TABLE #T (s char(128))

SELECT @b1=num_of_bytes_written from sys.dm_io_virtual_file_stats(2, 2) 
declare @i int = 0
while @i<20000
BEGIN
  insert into #T select '临时表:原值'
  set @i=@i+1
END

usetempdb
checkpoint
select @b2=num_of_bytes_written from sys.dm_io_virtual_file_stats(2, 2) 
select @b2-@b1 as日志增量   

--经测试,临时表日志增量  4851712

然后是表变量插入

useTestDB
declare @b1 bigint, @b2 bigint
declare @V table (s char(128))
select @b1=num_of_bytes_written from sys.dm_io_virtual_file_stats(2, 2) 

declare @i int = 0
while @i<20000
begin
  insert into @V select '表变量:原值'
  set @i=@i+1
end
usetempdb
checkpoint
select @b2=num_of_bytes_written from sys.dm_io_virtual_file_stats(2, 2) 
select @b2-@b1 as日志增量
--经测试,表变量日志增量5007360

两者日志记录相差不多,表变量还比临时表的日志写入更多

3)Lock上的不同表现

--临时表
if OBJECT_ID('tempdb..#T') is not null drop table#T

create table #T (s varchar(128))
insert into #T select '临时表:原值'

execute sp_lock @@spid  --查看当前用户进程的会话 所在的锁关系

BEGIN TRANSACTION
     update #T set s= '临时表:被更新'
     execute sp_lock @@spid  --发现增加了一个排他锁
ROLLBACK TRANSACTION

execute sp_lock @@spid  --排他锁被释放

GO

--表变量
declare @V table (s char(128))
insert into @V select '表变量:原值'

execute sp_lock @@spid

BEGIN TRANSACTION
     update @V set s='表变量:被更新'
     execute sp_lock @@spid
ROLLBACK TRANSACTION

execute sp_lock @@spid  --并没有在事务中加任何锁

临时表的更新会加锁,表变量更新不会

4)事务处理中的不同

if OBJECT_ID('tempdb..#T') is not null drop table#T

create table #T (s varchar(128))
declare @T table (s varchar(128))
insert into #T select '临时表:原值'
insert into @T select '表变量:原值'

BEGIN TRANSACTION
     update #T set s='临时表:被更新'
     update @T set s='表变量:被更新'
ROLLBACK TRANSACTION

select * from#T
select * from @T 

结果发现,临时表得值被回滚,表变量并没有回滚。可以得出,表变量不受事务影响

5)UDF中的不同

--表变量
CREATE FUNCTION dbo.example1 ( ) RETURNS INT AS 
BEGIN 
    DECLARE @t1 TABLE (i INT) 
    INSERT @t1 VALUES(1) 
    INSERT @t1 VALUES(2) 
    UPDATE @t1 SET i = i + 5 
    DELETE @t1 WHERE i < 7 
 
    DECLARE @max INT 
    SELECT @max = MAX(i) FROM @t1 
    RETURN @max 
END 
GO;
--临时表
CREATE FUNCTION dbo.example2 ( ) RETURNS INT 
AS 
BEGIN 
    CREATE TABLE #t1 (i INT) 
    INSERT #t1 VALUES(1) 
    INSERT #t1 VALUES(2) 
    UPDATE #t1 SET i = i + 5 
    DELETE #t1 WHERE i < 7 
 
    DECLARE @max INT 
    SELECT @max = MAX(i) FROM#t1 
    RETURN @max 
END  
GO
--物理表
CREATE FUNCTION dbo.example3 ( ) RETURNS INT AS 
BEGIN 
    CREATE TABLE table1 ( id INT IDENTITY, name VARCHAR(32) ) 
    INSERT table1(name) VALUES('aaron') 
    RETURN SCOPE_IDENTITY() 
END 

运行后,可以发现,函数内无法访问临时表,也无法创建实体表,同理也无法更新新增删除实体表的记录,但可以使用表变量来进行运算和操作

6) 性能对比

if OBJECT_ID('tempdb..#T') is not null drop table#T
create table #T (s char(1024))
declare @i int = 0
while @i<100000
begin
  insert into #T select '临时表:原值'
  set @i=@i+1
END
SELECT * FROM #T  --3秒左右--3秒
go

declare @V table (s char(1024))
declare @i int = 0
while @i<100000
begin
  insert into @V select '表变量:原值'
  set @i=@i+1
END
SELECT * FROM @V  --3秒左右,和临时表几乎一样

在插入性能上,两者基本一致

7)关联操作上,性能的不同表现

--构造数据
dbcc dropcleanbuffers; --从缓冲池中删除所有清除缓冲区

useTestDB

if object_id('tempdb..#temp') is not null drop table #temp                    

SELECT IDENTITY(INT, 1, 1) as_rowid, a.ppt, a.GoodsId
INTO #temp 
FROM  GraspFZDRPWrite001.dbo.Goods a  --goods表是一个有记录16049的数据表

GO

--用临时表
SELECT * FROM #temp
WHERE  _rowid IN (SELECT max(_rowid) FROM #temp GROUP  BYPpt) 
--很快 

--用表变量
DECLARE @PDTEMP TABLE (_rowid int, ppt CHAR(1), goodsid INT)
INSERT INTO @PDTEMP SELECT * FROM #temp

SELECT * FROM @PDTEMP   
WHERE  _rowid IN (SELECT max(_rowid) FROM  @PDTEMP GROUP  BYppt) 

go
--相当慢,记录几乎出不来

为啥会出现以上的结果呢,我们跟踪执行计划可以发现,前者执行计划选择的哈希匹配,后者则是相当缓慢的嵌套循环。

分析原因,因为聚合操作会利用表的统计信息来聚合,表变量没有统计信息,系统默认只能选择嵌套循环,而这导致严重的慢查询的主要原因

我们强制查询使用hash join连接(哈希匹配)

DECLARE @PDTEMP TABLE (_rowid int, ppt CHAR(1), goodsid INT)
INSERT INTO @PDTEMP SELECT * FROM #temp

SELECT * FROM @PDTEMP
WHERE  _rowid IN (SELECT max(_rowid) FROM  @PDTEMP GROUP  BYppt) 
option(hash join)          

这时速度跟用临时表一样,但不推荐这样使用,因为一旦这样强制使用,SQLSERVER的自动优化则不会起作用

8)把存储过程中返回的数据集插入到临时表 用于保存存储过程中返回的数据集

CREATE TABLE#sp_who3 
( 
    SPID INT, 
    Status VARCHAR(32) NULL)
go

create procedure pWho AS
  select 1 as spid, 'Tomas' asstatus
  union all
  select 1 as spid, 'Viviy' asstatus
go

insert #sp_who3 executepWho

select * from#sp_who3
     
DECLARE @PDTEMP TABLE (SPID INT, Status VARCHAR(32) NULL)
INSERT @PDTEMP EXECpWho 
SELECT * FROM @PDTEMP

GO

两者都可以正常使用,但是有一点必须注意,对于2008以前的版本,表变量是不支持这样操作的

9)是否可以动态的生成列

--临时表
SELECT * INTO #TEMP_objects FROM sys.objects; --成功执行

--表变量
SELECT * INTO @PDTEMP FROM sys.objects;   --报错

表变量不能生成动态列,因为表变量一旦创建,他的架构就是固定的,而临时表可以更改架构甚至是索引

10)生命期是否相同

if OBJECT_ID('tempdb..#temp_foo') is not null drop table#temp_foo

--临时表
DECLARE @sql VARCHAR(8000) 
SET @sql = 'Create TABLE #temp_foo (a INT,b INT,c INT)   Insert into #temp_foo values(1,1,1) ' 
EXEC(@sql) 
INSERT #temp_foo SELECT 1,2,3  
SELECT * FROM#temp_foo
GO

--表变量
DECLARE @sql VARCHAR(8000) 
SET @sql = 'DECLARE @foo TABLE(a INT,b INT,c INT) Insert into @foo values(1,1,1)' 
EXEC(@sql) 
INSERT @foo SELECT 1,2,3 
Go;

同时存在两个不同的批处理,外面的批处理要调用里面的批处理声明的表,临时表和表变量均不能使用

--临时表
DECLARE @sql VARCHAR(8000) 
Create TABLE #temp_foo (a INT,b INT,c INT)   
SET @sql = 'Insert into #temp_foo values(1,1,1)' 
EXEC(@sql) 
INSERT #temp_foo SELECT 1,2,3 
SELECT * FROM#temp_foo
GO 
 --可以执行

--表变量
DECLARE @sql VARCHAR(8000) 
DECLARE @foo TABLE(a INT,b INT,c INT)
SET @sql = 'Insert into @foo values(1,1,1)'  
EXEC(@sql) 
INSERT @foo SELECT 1,2,3 
SELECT * FROM @foo
Go
--执行到EXEC(@SQL)报错,找不到@foo

临时表在跨批处理中,里面的批处理可以调用到外面声明的表,表变量却不行(实体表只要是声明在前,都可以被调用)

总结:无表关联操作,只作为中间集进行数据处理,建议用表变量;有表关联,且不能确定数据量大小的情况下,建议用临时表。

免责声明:文章转载自《临时表与表变量深入探究》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇“C语言” 读书札记(六)之[Linux下C语言编程环境Make命令和Makefile] 川山甲 博客园DirectX学习笔记_关于Sprite.Draw2D的说明下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

如何优化mysql查询速度

1、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。 2、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 3、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id fr...

全网数据库大全

参考资源 数据库种类发展史和大数据下的数据库 NoSQL百度百科 NewSQL百度百科 分布式NewSQL对比——heaventouch大佬的博客 Google Spanner论文翻译——林子雨 人道浮沉的博客 列式数据库与行式数据库 Oracle数据库、MySQL数据库、SQL Server数据库 Oracle、Mysql、SQL Server比较 Mo...

反射+枚举+freemarker,自动生成实体类,自动建表建索引(二)之建表建索引,注解和DatabaseMetaData 获取信息

接上一篇博文 反射+枚举+freemarker,自动生成实体类,自动建表建索引(一)之生成实体类,枚举详解,这篇博文介绍自动建表。 用反射+枚举+freemarker,自己实现的自动生成实体类和自动建立数据表建索引。用enum枚举作为数据表的配置文件,1个枚举就是1张表,根据枚举类,自动生成实体类,和自动建表建索引。 主要步骤和 上一篇博文差不多,就是...

FastReport.Net使用:[21]表格(Table)控件

对表格控件的一些常用操作  合并单元格:选择需要合并的单元格(按住Shitf多选),然后在右键菜单中选择【合并单元格】。         2.删除/插入行 鼠标移到在行头,当鼠标状态变为向右的箭头时点击鼠标选中改行,然后右键鼠标显示右键菜单,选择删除等操作。 另外,在报表树中也能进行删除等操作。 3.编辑数据: 双击需要编辑的单元格,弹出文本编辑器进...

[原创]Spring JdbcTemplate 使用总结与经验分享

引言 近期开发的几个项目,均是基于Spring boot框架的web后端项目,使用JdbcTemplate执行数据库操作,实际开发过程中,掌握了一些有效的开发经验,踩过一些坑,在此做个记录及总结,与各位读者分享。 欢迎留言与我交流。 正确使用JdbcTemplate执行数据库操作 1、Bean声明 新增类型DatabaseConfiguration,添加注...

接口测试笔记

1.2 一..接口测试:通过相应的工具或者人工对某一个接口的工作状态进行测试的过程 二..接口测试点: 1.测试接口正确性:保证接口地址与请求方法是正确的 2.测试接口的安全性:有一些接口不能直接暴露,我们需要对它进行炎症之后才可以去调用 3.测试接口的性能:例如:我们需要考虑某一个接口在N多个用户访问的时候工作的压力 4.测试接口的数据:保证接口返回的数...