• 临时表与表变量深入探究


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

     

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

    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

     

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

     

     

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

     

     

  • 相关阅读:
    Eclipse中配置约束
    c++ 虚函数
    cocos3 menu
    cocos3 封装一个ball
    cocos3 内存管理机制
    cocos3 多文件拆分cocos
    cocos3 labelttf
    cocos3 messagebox
    cocos3 log
    cocos3 director sprite scene之间的关系
  • 原文地址:https://www.cnblogs.com/lanjun/p/2689056.html
Copyright © 2020-2023  润新知