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

sqlserver临时表Vs表变量详细介绍

说临时表和表变量,这是一个古老的话题,我们在网上也找到很多的资料阐述两者的特征,优点与缺点

说临时表和表变量,这是一个古老的话题,我们在网上也找到很多的资料阐述两者的特征,优点与缺点

这里我们在SQL Server 2005\SQL Server 2008版本上通过举例子,说明临时表和表变量两者的一些特征,让我们对临时表和表变量有进一步的认识。在本章中,我们将从下面几个方面去进行描述,对其中的一些特征举例子说明:

约束(Constraint) 索引(Index) I/0开销 作用域(scope) 存儲位置 其他

例子描述


约束(Constraint)

在临时表和表变量,都可以创建Constraint。针对表变量,只有定义时能加Constraint。

e.g.在Microsoft SQL Server Management Studio(MSSMS)查询中,创建临时表并建Constraint场景,<脚本S1.>

 tempdb

object_id()
#1

#1
(
ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate()),
PK_#1_ID (ID)
)

#1 CK_#1_Nr (Nr )


<脚本S1.>中,可以看出在临时表#1的创建时,创建Constraint如“Constraint PK_#1_ID Primary Key(ID)”,也可以在创建临时表#1后创建Constraint,如“Alter Table #1 Add Constraint CK_#1_Nr Check(Nr Between '10001' And'19999')”,下面我们来看表变量的场景,在定义表变量时不能指定Constraint名,定义表变量后不能对表变量创建Constraint。

e.g. 在定义表变量时不能指定Constraint名<代码S2.>

 tempdb

@1
(
ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate()),
[PK_@1_ID] (ID)
)

在定义表变量后不能对表变量创建Constraint,<代码S3.>

 tempdb

@1
(
ID ,
Nr nvarchar(50),
OperationTime datetime (getdate())
)

@1 [CK_@1_Nr] (Nr )

在<代码S2.>和<代码S3.>中可以发现,在解析T-SQL语法过程就发生错误,也就是SQL Server不支持定义表变量时对Constraint命名,也不支持定义表变量后,对其建Constraint。

这里慎重提示下,在<代码S1.>给临时表建Constraint的时候,特别是在并发场景中,不要指定具体的Constraint名称,不然会发生对象已存在的错误提示。

e.g. 在MSSMS中我们先执行之前<代码S1.>的创建临时表#1,不关闭当前会话的情况下,另建一个查询,执行与<代码S1.>相同的代码,如图

左边的查询窗口,是执行原先的<代码S1.>,右边的查询窗口,是后执行相同的<代码S1.>。在这里,我们注意红色圈圈部分,发现在创建临时表#1的过程,明确给了一个主键名称“PK_#1_ID”,当右边再创建相同临时表#1的时候就发生了对象重复错误问题。我们也可以通过SQL Server提供的系统视图sys.objects查询约束“PK_#1_ID”的信息,

 tempdb



* sys.objects name=

在系统视图sys.objects,发现“PK_#1_ID”名称后面不加如何的随机数值表述不同会话有不同的对象。根据SQL Server对sys.objects的描述规则,sys.objects中的Name列数据是唯一的。当另一个会话创建相同的对象时就会发生对象重复的错误。

在Constraint中,Foreign Key是不能应用与表变量,对于临时表,创建Foreign Key是没有意义的。也就是说临时表不受Foreign Key约束。下面我们通过例子来说明临时表的情况,

e.g.<脚本S4.>

 tempdb

object_id()
#1

object_id()
#2

#1
(

ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate()),
PK_#1_ID (ID)
)
#1 CK_#1_Nr (Nr )
#2
(
ID ,
ForeignID , (ForeignID) #1(ID)
)
Go

可以看出对于临时表不强制Foreign Key约束,我们也可以通过SQL Server系统视图sys.foreign_keys查询

 tempdb

* sys.tables name
* sys.foreign_keys

右边的查询,只看到在sys.tables表哦中存在刚才创建的临时表#1和#2,在sys.foreign_keys看不到有关Foreign Key约束信息。这也验证了左边SQL Server提示的,在临时表中无法强制使用Foreign Key约束。

索引(Index)

从索引方面看临时表和表变量,与从Constraint上分析有些类似,在临时表中,它与真实表一样可以创建索引。在表变量定义过程中,也可以创建一些类似唯一和聚集索引。

e.g.<脚本S5.>

 tempdb



@1 (

ID ,

Nr nvarchar(50)

)

Insert @1 (id,Nr) (1,)

Insert @1 (id,Nr) (2,)

Insert @1 (id,Nr) (8,)

Insert @1 (id,Nr) (3,)

Insert @1 (id,Nr) (7,)

2 *

sys.indexes a

sys.tables b b.object_id=a.object_id

b.create_date

Nr @1 Nr=


上面截的是两张图,第一张图描述在表变量使聚集Primary Key,创建非聚集的Unique约束,第二张图描述查询语句”Select Nr From @1 Where Nr='10005'” 应用到在表变量创建的唯一索引“UQ_#……”

是于临时表索引的例子,我们拿一个例子说明,与前边说的Constraint例子有点相似,这里我们对临时表创建索引,并给索引一个具体名称,测试是否会重复。

e.g.在MSSMS新增两个查询,编写下面的SQL语句:

<脚本S6.>

 tempdb

object_id()
#1

#1
(
ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate()),
)

IX_#1_Nr #1(Nr )

b.name TableName,
a.*
sys.indexes a
sys.tables b b.object_id=a.object_id
b.name
b.create_date Asc

从返回的结果,我们看到在系统视图表Sys.Indexes中,创建有两个相同的索引”IX_#1_Nr”,但注意下object_id数据不同。在SQL Server中是允许不同的表索引名称可以相同的。在并发的环境下,按原理是可以对临时表创建的索引给明确名称的。除非并发的情况会发生重复的表名或重复的Constraint,或其它系统资源不足的问题,才会导致出错。

I/0开销

临时表与表变量,在I/O开销的描述,我们直接通过一个特殊的例子去描述它们,在MSSMS上新增两个查询,分别输入临时表和表变量的测试代码:

e.g.<脚本S7.>临时表:

 tempdb

object_id()
#1

#1
(
ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate())
)

Insert #1(ID,Nr,OperationTime)
50000 row_number() ( a.object_id),(a.name+b.name,50) ,a.create_date
master.sys.all_objects a ,sys.all_columns b
type=



Nr,(Nr) Sum_
#1
Nr
Nr

<脚本S8.>表变量:

 tempdb

@1
(
ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate())
)

Insert @1(ID,Nr,OperationTime)
50000 row_number() ( a.object_id),(a.name+b.name,50) ,a.create_date
master.sys.all_objects a ,sys.all_columns b
type=


Nr,(Nr) Sum_
@1
Nr
Nr

<脚本S7.>和<脚本S8.>,主要是看最后的查询语句I/O的开销,两者有何不同。通过上面的运行结果图形描述,可以看出查询开始,不管是临时表还是表变量,都使用到了聚集索引扫描(Clustered Index Scan),两者虽然返回的数据一致,但I/O的开销不同。临时表的I/O开销是0.324606,而表变量只有0.003125 相差非常大。在临时表的执行计划图形中,我们发现一行“缺少索引(影响 71.9586):CREATE ……)”提示信息。我们对临时表#1,在字段“Nr”上创建一个非聚集索引,再看执行执行结果:

   IX_#1_Nr  #1(Nr)

我们在临时表#1上创建完索引“IX_#1_Nr”,运行看上面的图形显示,就感觉非常的有意思了。在临时表#1查询时用了索引搜索(Index Seek),而且I/O开销减少到了0.0053742。虽然开始查询的时候I/O开销还是比表变量开始查询的时候大一些,但执行步骤中比变变量少了一个“排序(Sort)”开销,后最后的看回Select结果,估计子树的成本比使用表变量的大大减少。

这里的例子只是描述一个特殊的情况,在真实的环境中,要根据实际的数据量来判断是否使用临时表或表变量。倘若在存储过程中,当数据量非常少如只有不到50行记录,数据占的页面也不会超过1个页面,那么使用表变量是一个很好的解决方案。

作用域(scope)

表变量像局部变量(local variable)一样,有着很窄的作用域,只能应用于定义的函数、存储过程或内。如,一个会话里面有几个,那么表变量只能作用在它定义所在的范围内。其他的无法再调用它。

e.g.在MSSMS新增一个查询,编写<脚本S9.>

 tempdb

Nocount
@1 (
ID ,
Nr nvarchar(50)
)
Insert @1 (id,Nr) (1,)
Insert @1 (id,Nr) (2,)
Insert @1 (id,Nr) (8,)
Insert @1 (id,Nr) (3,)
Insert @1 (id,Nr) (7,)

* @1

--结束点

* @1

<脚本S9.>所在的查询相当于一个会话,”Go”描述的一个的结束点。在”Go”之前定义的表变量,在”Go”之后调用是发生“必须声明变量”的错误提示。

临时表与表变量不同,临时表的作用域是当前会话都有效,一直到会话结束或者临时表被Drop的时候。也就是说可以跨当前会话的几个范围。

e.g.<脚本S10.>

 tempdb

object_id()
#1

#1
(
ID ,
Nr nvarchar(50) ,
OperationTime datetime (getdate()),
PK_#1_ID (ID)
)
* #1

--结束点

* #1

<脚本S10.>中可以看出在”GO”前后都可以查询到临时表#1。

在描述临时表与表变量的作用域时,有个地方要注意的是,当 sp_executesql 或 Execute 语句执行字符串时,字符串将作为它的自包含执行. 如果表变量在sp_executesql 或 Execute 语句之前定义,在sp_executesql 或 Execute 语句的字符串中无法调用外部定义的表变量。

e.g.<脚本S11.>

 tempdb

nocount
@1 (
ID ,
Nr nvarchar(50)
)
Insert @1 (id,Nr) (1,)
Insert @1 (id,Nr) (2,)
Insert @1 (id,Nr) (8,)
Insert @1 (id,Nr) (3,)
Insert @1 (id,Nr) (7,)

* @1

(N)


<脚本S11.>中,当执行到”Execute(N'Select * From @1')”时候,同样发生与<脚本S9.>一样的错误提示“必须声明变量@1”.

临时表是可以在sp_executesql 或 Execute 语句执行字符串中被调用。这里不再举例子,如果你有所模糊可以参考<脚本S11.>把表变量转成临时表测试下就能加深理解与记忆。

存儲位置

说到临时表和表变量的存储位置,我们可以看到有很多版本的说法,特别是表变量。有的说表变量数据存储在内存中,有的说存储在数据库tempdb中,有的说有部分存储在内存,部分存储在数据库tempdb中。根据我查到的官方资料,说的是在SQL Server 2000下:

A table variable is not a memory-only structure. Because a table variable might hold more data than can fit in memory, it has to have a place on disk to store data. Table variables are created in the tempdb database similar to temporary tables. If memory is available, both table variables and temporary tables are created and processed while in memory (data cache).

在SQL Server 2005\SQL2008的版本,表变量存储与临时表有相似,都会在数据库tempdb创建,使用到tempdb存储空间。

e.g.<脚本S12.>临时表

 tempdb

nocount

sp_spaceused

object_id()
#1

#1(ID ,Nr nvarchar(50))
Insert #1 (ID,Nr)
(1) row_number() ( a.object_id),(a.name+b.name,50)
sys.all_objects a,
sys.all_columns b

(1) name,object_id,type,create_date sys.tables create_date

sp_spaceused



在<脚本S12.>执行后,我们可以看到在数据库tempdb中的表sys.tables创建有表#1。我们接着看空间的使用情况,插入数据之前,数据库未使用空间(unallocated space)为510.39MB,向临时表#1插入1条数据后,数据库未使用空间为501.38MB,未使用空间变小了。再来看整个数据库的数据(data)使用的空间变化,从552KB变成560KB,使用了一页的数据空间(8kb)。这说明一点,临时表,即使你只插入一条数据都会使用到数据库tempdb的空间。也许会有人问,要是我只建临时表#1,不插入数据,会如何。我们可以结果:

这里你会发现前后的空间大小不变,不过,不要认为没有使用到数据库tempdb数据空间,当你多用户创建临时表结构的时候,你就会发现其实都会应用到数据库tempdb的空间。我这里创建了10个#1后的效果如:

相同的原理,我们使用类似的方法测试表变量的情况,发现结论是与临时表的一致的,会使用到数据库tempdb的空间.

e.g.<脚本S13.>表变量

 tempdb

nocount
sp_spaceused

@1 (ID ,Nr nvarchar(50))
Insert @1 (ID,Nr)
(1) row_number() ( a.object_id),(a.name+b.name,50)
sys.all_objects a,
sys.all_columns b

(1) name,object_id,type,create_date sys.objects type= create_date

sp_spaceused


sp_spaceused

<脚本S13.>中,我多写了一个”GO”之后检查空间大小的存储过程sp_spaceused。这样为了了更能体现表变量使用空间变化情况。从插入数据前和插入数据后的结果图来看,表变量不仅在数据库tempdb创建了表结构#267ABA7A类似的这样表,表变量也应用到了数据库tempdb的空间。不过这里注意一点就是在”Go”之后,我们发现表变量@1,会马上释放所使用的数据空间。为了更能体现使用空间情况。我们可以向表变量@1插入大量数据看空间变化情况(测试插入1000万的数据行)。

e.g.<脚本S14.>

 tempdb

nocount
sp_spaceused

@1 (ID ,Nr nvarchar(50))
Insert @1 (ID,Nr)
(10000000) row_number() ( a.object_id),(a.name+b.name,50)
sys.all_objects a,
sys.all_columns b

(1) name,object_id,type,create_date sys.objects type= create_date

sp_spaceused


sp_spaceused

这里我们可清晰的看到数据库tempdb的大小(database_size)变化情况,从插入数据前的552.75MB变成插入数据之后的892.75MB。非常有意思的是我们在”Go之后”发现数据库大小保存在892.75MB,但数据使用空间(data)从560KB—>851464KB—>536KB ,说明SQL Server自动释放为使用的数据空间,但不会马上自动释放数据库分配的磁盘空间。我们在实际的环境中,发现临时数据库tempdb使用的磁盘空间越来越大,这是其中的原因之一。

其他

临时表与表变量,还有其他的特征,如临时表受事务回滚,而表变量不受事务回滚影响。对应事务方面,更为正确的说法是表变量的事务只在表变量更新期间存在。因此减少了表变量对锁定和记录资源的需求。

e.g.<脚本S15.>

 tempdb

nocount

object_id()
#1
#1(ID ,Nr nvarchar(50))
@1 (ID ,Nr nvarchar(50))



Insert #1 (ID,Nr)
(1) row_number() ( a.object_id),(a.name+b.name,50)
sys.all_objects a,
sys.all_columns b


Insert @1 (ID,Nr)
(1) row_number() ( a.object_id),(a.name+b.name,50)
sys.all_objects a,
sys.all_columns b



* #1
* @1



这里发现”Rollback Tran”之后,临时表#1没有数据插入,而表变量@1还有一条数据存在。说明表变量不受”Rollback Tran”所影响。它的行为有类似于局部变量一样。

另外SQL Server对表变量不保留任何的统计信息,因为如此,我们在数据量大的时候使用表变量,发现比临时表要慢许多。前面在I/O开销那里我们取有一个特殊的例子,这里不再举例。

小结

无论如何,临时表和表变量有各自的特征,有自己优点和缺点。在不同的场景选择它们灵活应用。本文章是我对临时表和表变量的一些认识理解,可能有些地方说的不够好或者遗漏,你可以留言或Email与我联系,我会继续改进或纠正,我也不希望有些错误的见解会误导别人。正如说的一句" I'd hate to think of anyone being misled by my advice!".

附参考:

推荐阅读
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了在SQL中查询分组后每组行数的统计方法。通过使用count()函数和GROUP BY子句可以统计每组的行数,但是如何统计所有组的行数呢?本文提供了一种实现方法,并给出了相应的SQL查询语句。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • Echarts图表重复加载、axis重复多次请求问题解决记录
    文章目录1.需求描述2.问题描述正常状态:问题状态:3.解决方法1.需求描述使用Echats实现了一个中国地图:通过选择查询周期&#x ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • Python字典推导式及循环列表生成字典方法
    本文介绍了Python中使用字典推导式和循环列表生成字典的方法,包括通过循环列表生成相应的字典,并给出了执行结果。详细讲解了代码实现过程。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • “你永远都不知道明天和‘公司的意外’哪个先来。”疫情期间,这是我们最战战兢兢的心情。但是显然,有些人体会不了。这份行业数据,让笔者“柠檬” ... [详细]
author-avatar
玉龙惊云诱惑_786_286
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有