热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

表变量与临时表的应用优势及潜在局限性分析

长期以来,关于临时表与表变量的优劣之争一直存在,部分技术社区甚至认为表变量几乎毫无用武之地,如缺乏统计信息、不支持事务处理等。然而,实际情况并非如此绝对。本文将从多个角度对比分析临时表与表变量,探讨它们在不同场景下的应用优势及其潜在局限性,帮助开发者更好地选择合适的数据结构。

一直以来大家对临时表与表变量的孰优孰劣争论颇多,一些技术群里的朋友甚至认为表变量几乎一无是处,比如无统计信息,不支持事务等等.但事实并非如此.这里我就临时表与表变量做个对比,对于大多数人不理解或是有歧义的地方进行详细说明.

注:这里只讨论一般临时表,对全局临时表不做阐述.

生命周期

临时表:会话中,proc中,或使用显式drop

表变量:batch中

这里用简单的code说明表变量作用域

DECLARE @t TABLE(i int) ----定义表变量@tSELECT *FROM @t -----访问OKinsert into @t select 1 -----插入数据OKselect * from @t -------访问OK
go -------结束批处理
select * from @t -------不在作用域出错

注意:虽然说sqlserver在定义表变量完成前不允许你使用定义的变量.但注意下面情况仍然可正常运行!

if 'a'='b'
begin
DECLARE @t TABLE(i int)
end
SELECT *FROM @t -----仍然可以访问!

日志机制

临时表与表变量都会记录在tempdb中记录日志

不同的是临时表的活动日志在事务完成前是不能截断的.

这里应注意的是由于表变量不支持truncate,所以完全清空对象结果集时临时表有明显优势,而表变量只能delete

事务支持

临时表:支持

表变量:不支持

我们通过简单的实例加以说明

create table #t (i int)
declare @t table(i int)

BEGIN TRAN ttt
insert into #t select 1
insert into @t select 1
SELECT * FROM #t ------returns 1 rows
SELECT * FROM @t ------returns 1 rows
ROLLBACK tran ttt

SELECT * FROM #t -------no rows
SELECT * FROM @t -------still 1 rows
drop table #t ----no use drop @t in session

 

锁机制(select)

临时表 会对相关对象加IS(意向共享)锁

表变量 会对相关对象加SCH-S(架构共享)锁(相当于加了nolock hint)

可以看出虽说锁的影响范围不同,但由于作用域都只是会话或是batch中,临时表的IS锁虽说兼容性不如表变量的SCH-S但绝大多数情况基本无影响.

感兴趣的朋友可以用TF1200测试

索引支持

临时表  支持

表变量  条件支持(仅SQL2014)

没错,在sql2014中你可以在创建表的同时创建索引 图1-1

注:在sql2014之前表变量只支持创建一个默认的唯一性约束
code

DECLARE @t TABLE
(
col1 int index inx_1 CLUSTERED,
col2 int index index_2 NONCLUSTERED,index index_3 NONCLUSTERED(col1,col2)
)

                                图1-1

 

用户自定义函数(UDFs)

临时表 不支持作为UDF的结果集返回

表变量 支持作为UDF的结果集返回

注:当表变量作为UDF的结果集返回时分为TVF(Table-Valued Function),TVP(Table-Valued Parameters)两种类型,只有TVF支持plan cache

如图1-2
Code

CREATE FUNCTION TVP_Customers (@cust nvarchar(10))
RETURNS TABLE
ASRETURN(SELECT RowNum, CustomerID, OrderDate, ShipCountryFROM BigOrdersWHERE CustomerID = @cust);
GO
CREATE FUNCTION TVF_Customers (@cust nvarchar(10))
RETURNS @T TABLE (RowNum int, CustomerID nchar(10), OrderDate date,ShipCountry nvarchar(30))
AS
BEGININSERT INTO @TSELECT RowNum, CustomerID, OrderDate, ShipCountryFROM BigOrdersWHERE CustomerID = @custRETURN
END;DBCC FREEPROCCACHE
GO
SELECT * FROM TVF_Customers('CENTC');
GO
SELECT * FROM TVP_Customers('CENTC');
GO
SELECT * FROM TVF_Customers('SAVEA');
GO
SELECT * FROM TVP_Customers('SAVEA');
GOselect b.text,a.execution_count,a.* from sys.dm_exec_query_stats a
cross apply sys.dm_exec_sql_text(a.sql_handle) b
where b.text like '%_Customers%'

 

                                                                        图1-2

 

其它方面

表变量不支持select into,alter,truncate,dbcc等

表变量不支持table hint 如(force seek)

 

执行计划预估

我想这里可能是引起使用何种方式争论比较突出的地方,由于表变量没有统计信息,无法添加索引等使得大家对其在执行计划中的性能表现嗤之以鼻,但实际情况呢?我们需要深入分析.

关于临时表的预估这里我就不做介绍了,主要对表变量的预估做详细阐述.

表变量在sql2000引入的一个原因就是为了在一些执行过程中减少重编译.以获得更好的性能.当然带来好处的同时也会带来一定弊端.由于其不涉及重编译,优化器其实并不知道表变量中的具体行数,此时他采取了保守的预估方式:预估行数为1行.如图2-1

 Code

declare @t table (i int)
select * from @t-----此时0行预估行数为1行
insert into @t select 1
select * from @t-----此时1行,预估行数仍为1行
insert into @t values (2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(14),(15),(16),(17),(18),(19),(20)
select * from @t ----此时19行,预估行数仍为1行--....无论实际@t中有多少行,由于没有重编译,预估均为1行

 

 

                                                                             图2-1

 所以当我们加上重编译的的操作,此时优化器就知道了表变量的具体行数.如图2-2

Code

 

declare @t table (i int)
select * from @t option(recompile)-----此时0行预估行数为1行
insert into @t select 1
select * from @t option(recompile)-----此时1行,预估行数为1行
insert into @t values (2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(14),(15),(16),(17),(18),(19),(20)
select * from @t option(recompile)----此时19行,预估行数为19行
--....当加入重编译hint时,优化器就知道的表变量的行数.

 

                                                                     图2-2

 

至此,我们可以看到优化器知道了表变量中的行数.这样在表变量扫描的过程中,尤其针对数据量较大的情形,不会因为预估总是1而引起一些问题.

如果你刚知道这里的预估原理,现有的代码都加上重编译那工作量可想而知了..这里介绍一个新的跟踪标记,Trace Flag 2453.

TF2453可以一定程度上替代重编译Hint,但只是在非简单计划(trivial plans)的情形下

注:TF2453只在sql2012 SP2和SQL2014中的补丁中起作用

表变量谓词预估

由于表变量木有统计信息,在优化器知道整体行数的前提下将会根据谓词的情形

采用不同的规则"猜"来进行预估.

注:这里有些规则笔者未找到微软相应的算法文档,经过自己根据数据推算得出.

看到这里的朋友请为我点个赞J(很长时间推算得出.可能数学忘得差不多了)

注:由于检索对象本身及为变量,谓词为变量,或是常数无影响

常见谓词下预估算法:

a ">", "<" 运算符 按照表变量数据量的30%进行预估

b "like" 运算符 按照表变量数据量的10%进行预估

c "&#61;"  运算符 按照表变量数据量的0.75次方预估

实例如图2-3

code

declare &#64;i int
set &#64;i&#61;13
DECLARE &#64;T TABLE(I INT);
INSERT INTO &#64;T VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(14),(15),(16),(17),(18),(19),(20)
------表变量中存在个数字
select * from &#64;T where I <1 option(recompile) ------20*30% 预估数为6
select * from &#64;T where I > &#64;i option(recompile) --------20*30%预估数为6
select * from &#64;T where I like &#64;i option(recompile) --------20*10% 预估数为2
select * from &#64;T where I like 1 option(recompile) --------20*10 预估数为2
select * from &#64;T where I &#61; &#64;i option(recompile) --------POWER(20.00000,0.75) 预估数为9.45742
select * from &#64;T where I &#61; 1 option(recompile) --------POWER(20.00000,0.75) 预估数为9.45742insert into &#64;T
select DatabaseLogID from AdventureWorks2008R2.dbo.DatabaseLog------insert new records
select * from &#64;T option(recompile) ------------此时数据为行
select * from &#64;T where I &#61; 1 option(recompile)--------------------POWER(1617.00000,0.75) 预估数为254.99550

 

                                                                         图2-3

 可以看出根据不同的谓词优化器会采用不同的预估方式,虽然它不如统计信息下的密度,直方图等来的精确(尤其是等值预估,在数据量巨大的情形下,其效果可能接近统计信息),但在了解数据的前提下如果适合表变量我们还是可以大胆使用的.

Tempdb竞争

tempdb的竞争本身涵盖的知识面比较大,这里我们只讨论临时表与表变量的孰优孰劣.

通过前面的介绍我们知道临时表是支持事务的,而表变量时不支持的.正因如此很多人放弃了表变量的使用.但任何事情都有两方面,支持就一定好吗?由于临时表对事务的支持,在高并发的情形中可能正因为其事务的支持造成系统表锁,总而影响并发.

 

我们通过一个简单的实例来说明

日常管理中,我发现很多开发人员在使用临时表时采用select * into #t from …的语法,这样的写法如果数据量稍大,将会造成事务持有系统表锁的时间变长,从而影响并发,吞吐.我们通过一个简单的实例说明.如图3-1

 

Code 我们通过sqlquerystress模拟并发

----SSMS测试数据
Use tempdb
create table t
( id int identity,str1 char(8000))----more pages for many recordsinsert into t select &#39;a&#39;
go 100----sqlquerystress
select * into #t
from t----57s----sqlquerystress
declare &#64;t table
( id int,str1 char(8000))
insert into &#64;t
select * from t-----1s

 

 

                                                                           图3-1

 

通过图3-1可以看出上述情形中临时表简直不堪重负.临时表与表变量到底该如何应用不是看谁比谁的优点多,应视具体情形而定

结语:借用火影忍者中宇智波. 鼬的一句名言&#xff1a;”任何术都是有缺陷的” 同样,在数据库的世界里没有哪项技术是完美无缺的.根据实际的场景,情形,选择合理的实现方式才是我们的初衷.

转:https://www.cnblogs.com/lsgsanxiao/p/10915247.html



推荐阅读
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • CentOS7源码编译安装MySQL5.6
    2019独角兽企业重金招聘Python工程师标准一、先在cmake官网下个最新的cmake源码包cmake官网:https:www.cmake.org如此时最新 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 在前两篇文章中,我们探讨了 ControllerDescriptor 和 ActionDescriptor 这两个描述对象,分别对应控制器和操作方法。本文将基于 MVC3 源码进一步分析 ParameterDescriptor,即用于描述 Action 方法参数的对象,并详细介绍其工作原理。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 利用存储过程构建年度日历表的详细指南
    本文将介绍如何使用SQL存储过程创建一个完整的年度日历表。通过实例演示,帮助读者掌握存储过程的应用技巧,并提供详细的代码解析和执行步骤。 ... [详细]
  • 在使用SQL Server进行动态SQL查询时,如果遇到LIKE语句无法正确返回预期结果的情况,通常是因为参数传递方式不当。本文将详细探讨这一问题,并提供解决方案及相关的技术背景。 ... [详细]
  • 本文介绍如何通过创建替代插入触发器,使对视图的插入操作能够正确更新相关的基本表。涉及的表包括:飞机(Aircraft)、员工(Employee)和认证(Certification)。 ... [详细]
  • SQLite 动态创建多个表的需求在网络上有不少讨论,但很少有详细的解决方案。本文将介绍如何在 Qt 环境中使用 QString 类轻松实现 SQLite 表的动态创建,并提供详细的步骤和示例代码。 ... [详细]
  • 根据最新发布的《互联网人才趋势报告》,尽管大量IT从业者已转向Python开发,但随着人工智能和大数据领域的迅猛发展,仍存在巨大的人才缺口。本文将详细介绍如何使用Python编写一个简单的爬虫程序,并提供完整的代码示例。 ... [详细]
  • ASP.NET MVC中Area机制的实现与优化
    本文探讨了在ASP.NET MVC框架中,如何通过Area机制有效地组织和管理大规模应用程序的不同功能模块。通过合理的文件夹结构和命名规则,开发人员可以更高效地管理和扩展项目。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
author-avatar
没有水的鱼0713
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有