zoukankan      html  css  js  c++  java
  • 临时表与表变量深入探究

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

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

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

    1) 关于存储

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

    复制代码
    use tempdb  
    exec sp_spaceused
    
    --database_name    database_size    unallocated space
    --tempdb        8.50 MB            6.75 MB
    
    --tempdb数据库占用8.50M,未用空間6.75M
    
    use TestDB
    
    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条记录
    
    use tempdb
    CHECKPOINT  --写入MDF中
    
    exec sp_spaceused
    
    --database_name    database_size    unallocated space
    --tempdb        8.50 MB            6.62 MB
    复制代码

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

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

    复制代码
    use tempdb
    exec sp_spaceused
    
    --database_name    database_size    unallocated space
    --tempdb        8.50 MB            6.69 MB
    
    use TestDB
    
    declare @Table3 table (id int,  atypeid char(1024))
    declare @count int = 50
    
    INSERT INTO @Table3(id, atypeid)
    SELECT TOP(@count) 1 as id, 'sss' as atypeid
    FROM GraspFZDRPWrite001.dbo.BillType 
    
    use tempdb
    checkpoint
    
    exec sp_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
    
    use tempdb
    checkpoint
    select @b2=num_of_bytes_written from sys.dm_io_virtual_file_stats(2, 2) 
    select @b2-@b1 as 日志增量   
    
    --经测试,临时表日志增量  4851712
    复制代码

    然后是表变量插入

    复制代码
    use TestDB
    
    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
    use tempdb
    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; --从缓冲池中删除所有清除缓冲区
    
    use TestDB
    
    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  BY Ppt) 
    --很快 
    
    --用表变量
    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  BY ppt) 
    
    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  BY ppt) 
    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' as status
      union all
      select 1 as spid, 'Viviy' as status
    go
    
    insert #sp_who3 execute pWho
    
    select * from #sp_who3
         
    DECLARE @PDTEMP TABLE (SPID INT, Status VARCHAR(32) NULL)
    INSERT @PDTEMP EXEC pWho 
    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
    复制代码

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

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

  • 相关阅读:
    springmvc log4j 配置
    intellij idea maven springmvc 环境搭建
    spring,property not found on type
    intellij idea maven 工程生成可执行的jar
    device eth0 does not seem to be present, delaying initialization
    macos ssh host配置及免密登陆
    centos7 搭建 docker 环境
    通过rest接口获取自增id (twitter snowflake算法)
    微信小程序开发体验
    gitbook 制作 beego 参考手册
  • 原文地址:https://www.cnblogs.com/soundcode/p/2690020.html
Copyright © 2011-2022 走看看