在本文中,您将会了解到关于SQLServer中的临时表和表变量的新资讯,同时我们还将为您解释sqlserver临时表详解的相关在本文中,我们将带你探索SQLServer中的临时表和表变量的奥秘,分析s
在本文中,您将会了解到关于SQL Server中的临时表和表变量的新资讯,同时我们还将为您解释sqlserver临时表详解的相关在本文中,我们将带你探索SQL Server中的临时表和表变量的奥秘,分析sqlserver临时表详解的特点,并给出一些关于SQL Server 2008中的临时表上的sql-server – nolock、SQL Server 中的临时表和表变量有什么区别?、SQL Server 临时表与表变量的区别分析、sql server 表变量、表类型、临时表的实用技巧。
本文目录一览:- SQL Server中的临时表和表变量(sqlserver临时表详解)
- SQL Server 2008中的临时表上的sql-server – nolock
- SQL Server 中的临时表和表变量有什么区别?
- SQL Server 临时表与表变量的区别分析
- sql server 表变量、表类型、临时表
SQL Server中的临时表和表变量(sqlserver临时表详解)
在sql Server的性能调优中,有一个不可比拟的问题:那就是如何在一段需要长时间的代码或被频繁调用的代码中处理临时数据集?表变量和临时表是两种选择。
在sql Server的性能调优中,有一个不可比拟的问题:那就是如何在一段需要长时间的代码或被频繁调用的代码中处理临时数据集?表变量和临时表是两种选择。记得在给一家国内首屈一指的海运公司作sql Server应用性能评估和调优的时候就看到过大量的临时数据集处理需求,而他们的开发人员就无法确定什么时候用临时表,什么时候用表变量,因此他们就简单的使用了临时表。实际上临时表和表变量都有特定的适用环境。
先卖弄一些基础的知识:
表变量
变量都以@或@@为前缀,表变量是变量的一种,另外一种变量被称为标量(可以理解为标准变量,就是标准数据类型的变量,例如整型int或者日期型DateTime)。以@前缀的表变量是本地的,因此只有在当前用户会话中才可以访问,而@@前缀的表变量是全局的,通常都是系统变量,比如说@@error代表最近的一个T-sql语句的报错号。当然因为表变量首先是个变量,因此它只能在一个Batch中生存,也就是我们所说的边界,超出了这个边界,表变量也就消亡了。
表变量存放在内存中,正是因为这一点所有用户访问表变量的时候sql Server是不需要生成日志。同时变量是不需要考虑其他会话访问的问题,因此也不需要锁机制,对于非常繁忙的系统来说,避免锁的使用可以减少一部分系统负载。
表变量另外还有一个限制就是不能创建索引,当然也不存在统计数据的问题,因此在用户访问表变量的时候也就不存在执行计划选择的问题了(也就是以为着编译阶段后就没有优化阶段了),这一特性有的时候是件好事,而有些时候却会造成一些麻烦。
临时表
临时对象都以#或##为前缀,临时表是临时对象的一种,还有例如临时存储过程、临时函数之类的临时对象,临时对象都存储在tempdb中。以#前缀的临时表为本地的,因此只有在当前用户会话中才可以访问,而##前缀的临时表是全局的,因此所有用户会话都可以访问。临时表以会话为边界,只要创建临时表的会话没有结束,临时表就会持续存在,当然用户在会话中可以通过DROP TABLE命令提前销毁临时表。
我们前面说过临时表存储在tempdb中,因此临时表的访问是有可能造成物理IO的,当然在修改时也需要生成日志来确保一致性,同时锁机制也是不可缺少的。
跟表变量另外一个显著去别就是临时表可以创建索引,也可以定义统计数据,因此sql Server在处理访问临时表的语句时需要考虑执行计划优化的问题。
表变量 vs. 临时表
结论
综上所述,大家会发现临时表和表变量在底层处理机制上是有很多差别的。
简单地总结,我们对于较小的临时计算用数据集推荐使用表变量。如果数据集比较大,如果在代码中用于临时计算,同时这种临时使用永远都是简单的全数据集扫描而不需要考虑什么优化,比如说没有分组或分组很少的聚合(比如说COUNT、SUM、AVERAGE、MAX等),也可以考虑使用表变量。使用表变量另外一个考虑因素是应用环境的内存压力,如果代码的运行实例很多,就要特别注意内存变量对内存的消耗。
一般对于大的数据集我们推荐使用临时表,同时创建索引,或者通过sql Server的统计数据(Statisitcs)自动创建和维护功能来提供访问sql语句的优化。如果需要在多个用户会话间交换数据,当然临时表就是唯一的选择了。需要提及的是,由于临时表存放在tempdb中,因此要注意tempdb的调优。
再议sql Server临时表和表变量
今天在我和一家软件公司的开发人员讨论数据库设计调优的时候又讨论到了表变量和临时表的问题,觉得这个问题确实是一个争议比较大的问题。
其实从上次发表了表变量和临时表的一个帖子http://database.ctocio.com.cn/tips/442/8206442.shtml以来,也有些人留言,也有些人发过邮件讨论这个问题。其实表变量和临时表的区别虽然有一些,但是两者最根本的区别还是在于
对存储的需求:表变量和临时表都消耗Tempdb中的存储空间,但是进行数据更新的时候,表变量不会写日志,而临时表则会写日志。(这一点是经过脚本测试的,表变量并不像我们想象的那样,只写在内存而不出现在Tempdb中。)
对优化的支持:表变量不支持索引和统计数据,临时表则可以支持索引和统计数据。
通常需要表变量或者临时表的情况都是一些需要支持临时计算结果集的地方,那么就有一些常见的情况了:
如果临时结果集仅仅需要往里面写数据,比如通过一个循环多次查找相关数据并合成一个临时结果集,那么就可以使用表变量。(结果有人提到了返回结果集的时候需要有排序,但是表变量不支持索引阿。其实这个不要紧,因为表变量虽然不支持索引,但是表变量支持主键阿,所以可以利用主键来替代索引。)
如果临时结果集不太多需要更改,而是更多地充当一个临时的关联数据集去参加各种数据集的连接(JOIN),那么索引和统计数据可能会更加适合一些(当然这个临时结果集要足够大,这样索引和统计数据带来的代价才可以被弥补掉)。
由于表变量不支持统计数据,因此在一个存储过程中使用表变量可以减少由于数据变化而导致的重新编译问题。
当然,除了索引和统计数据这个明显的限制外,表变量同时也不支持并行执行计划,因此对于大型的临时结果集,表变量也不是一个好的选择。
前面一个关于表变量和临时表的贴子,有一位robi_xu的朋友提到的问题也确实是在选择表变量和临时表时候的一些问题。
对于函数中不能支持临时表是由于函数不能对函数作用域外部的资源状态造成永久性的更改,在sqlServer中也称为副作用(sideeffect)。不过如果在函数中使用大型的临时结果集是不推荐的,因为如果将这样的函数放置到一个查询中会造成很明显的性能问题,因此这种情况一般都采用存储过程之类的批处理脚本。
对于动态脚本不支持表变量的原因是因为存储过程不接受表类型的参数。不过如果表变量的声明和赋值都在sp_executesql的参数中的话,sp_executesql就可以执行了,因为这个时候表变量就存在sp_executesql的stmt参数里面,不需要传入,例如下面的代码:(当然这样的实用性也就没有多少了)
DECLARE @m nvarchar(max)
SET @m = N"DECLARE @t TABLE (ID int);INSERT INTO @tVALUES(1);SELECT * FROM @t T"
EXEC sp_executesql @m
作者:DrillChina
From:
http://database.ctocio.com.cn/tips/442/8206442.shtml
http://it.hexun.com/2008-07-09/107302733.html
SQL Server 2008中的临时表上的sql-server – nolock
PS:是的,我知道READUNCOMMITTED的危险.
select * from #myTempTable
VS
select * from #myTempTable with (nolock) --is this faster?
解决方法
SET NOCOUNT ON; CREATE TABLE ##T ( X INT ) INSERT INTO ##T SELECT number FROM master..spt_values CREATE TABLE #T ( X INT ) INSERT INTO #T SELECT * FROM ##T /*Run the commands first with the trace flag off so the locking info is less full of irrelevant stuff about plan compilation */ GO PRINT '##T Read Committed' SELECT COUNT(*) FROM ##T PRINT '##T NOLOCK' SELECT COUNT(*) FROM ##T WITH (NOLOCK) PRINT '##T Finished' GO PRINT '#T Read Committed' SELECT COUNT(*) FROM #T PRINT '#T NOLOCK' SELECT COUNT(*) FROM #T WITH (NOLOCK) PRINT '#T Finished' GO DBCC TRACEON(-1,3604) DBCC TRACEON(-1,1200) GO PRINT '##T Read Committed' SELECT COUNT(*) FROM ##T PRINT '##T NOLOCK' SELECT COUNT(*) FROM ##T WITH (NOLOCK) PRINT '##T Finished' GO PRINT '#T Read Committed' SELECT COUNT(*) FROM #T PRINT '#T NOLOCK' SELECT COUNT(*) FROM #T WITH (NOLOCK) PRINT '#T Finished' GO DBCC TRACEOFF(-1,3604) DBCC TRACEOFF(-1,1200) DROP TABLE ##T DROP TABLE #T
对于一个全球的临时表,这并不奇怪,使得更多的区别.
尽管如此,本地#temp表的锁类型仍然存在较小差异.我再现下面输出的那部分输出
#T Read Committed Process 56 acquiring IS lock on OBJECT: 2:301244128:0 (class bit0 ref1) result: OK Process 56 acquiring S lock on OBJECT: 2:301244128:0 (class bit0 ref1) result: OK Process 56 releasing lock on OBJECT: 2:301244128:0 #T NOLOCK Process 56 acquiring Sch-S lock on OBJECT: 2:301244128:0 (class bit0 ref1) result: OK Process 56 acquiring S lock on HOBT: 2:9079256880114171904 [BULK_OPERATION] (class bit0 ref1) result: OK Process 56 releasing lock on OBJECT: 2:301244128:0
编辑:上面的结果是一个堆.对于具有聚簇索引的临时表,结果如下.
#T Read Committed Process 55 acquiring IS lock on OBJECT: 2:1790629422:0 (class bit0 ref1) result: OK Process 55 acquiring S lock on OBJECT: 2:1790629422:0 (class bit0 ref1) result: OK Process 55 releasing lock on OBJECT: 2:1790629422:0 #T NOLOCK Process 55 acquiring Sch-S lock on OBJECT: 2:1790629422:0 (class bit0 ref1) result: OK Process 55 releasing lock on OBJECT: 2:1790629422:0 #T Finished
堆版本上的BULK_OPERATION锁的原因是explained here.但是可以看出,锁定开销是非常小的.
SQL Server 中的临时表和表变量有什么区别?
这似乎是一个有很多神话和相互矛盾的观点的领域。
那么 SQL Server 中的表变量和本地临时表有什么区别呢?
答案1
小编典典内容
警告
这个答案讨论了 SQL Server 2000 中引入的“经典”表变量。内存 OLTP 中的 SQL Server 2014 引入了内存优化表类型。这些表变量实例在许多方面与下面讨论的不同!
存储位置
没有不同。两者都存储在tempdb中。
我已经看到它表明对于表变量,情况并非总是如此,但这可以从下面验证
DECLARE @T TABLE(X INT)INSERT INTO @T VALUES(1),(2)SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]FROM @T
示例结果(在tempdb中显示存储 2 行的位置)
File:Page:Slot----------------(1:148:0)(1:148:1)
逻辑位置
与表相比,表变量的行为更像是当前数据库的一部分#temp
。对于表变量(自 2005 年以来),如果未明确指定列排序规则,则将是当前数据库的列排序规则,而对于#temp
表,它将使用tempdb的默认排序规则(更多详细信息)。
用户定义的数据类型和 XML 集合必须在tempdb中才能用于#temp
表和表变量 ( Source )。表的tempdb中必须存在用户定义的别名类型,但表变量使用上下文数据库。#temp
SQL Server 2012 引入了包含的数据库。这些临时表的行为不同(h/t Aaron)
在包含的数据库中,临时表数据在包含的数据库的排序规则中进行排序。
- 与临时表关联的所有元数据(例如,表和列名、索引等)都将在目录排序规则中。
- 命名约束不能在临时表中使用。
- 临时表可能不引用用户定义的类型、XML 模式集合或用户定义的函数。
不同范围的可见性
表变量只能在声明它们的批处理和范围内访问。#temp
可以在子批次(嵌套触发器、过程、exec
调用)中访问表。#temp
在外部范围 ( @@NESTLEVEL=0
) 创建的表也可以跨越批次,因为它们会一直持续到会话结束。两种类型的对象都不能在子批处理中创建并在调用范围内访问,但是如下所述(尽管可以##temp
是全局表)。
寿命
DECLARE @.. TABLE
执行包含语句的批处理时(在该批处理中的任何用户代码运行之前)隐式创建表变量,并在最后隐式删除。
尽管解析器不允许您在DECLARE
语句之前尝试使用表变量,但可以在下面看到隐式创建。
IF (1 = 0)BEGINDECLARE @T TABLE(X INT)END--Works fineSELECT *FROM @T
#temp
表在CREATE TABLE
遇到 TSQL 语句时显式创建,并且可以在批处理结束时显式删除DROP TABLE
或将在批处理结束时隐式删除(如果在子批处理中创建@@NESTLEVEL > 0
)或会话结束时以其他方式结束。
注意:在存储例程中,两种类型的对象都可以被缓存,而不是重复创建和删除新表。对于何时可以发生这种缓存有一些限制,但是对于#temp
表可能会违反这些限制,但对表变量的限制无论如何都会阻止这些限制。#temp
缓存表的维护开销略大于表变量
对象元数据
这对于两种类型的对象基本上是相同的。它存储在tempdb的系统基表中。#temp
但是,对于表 来说,查看起来更直接,因为OBJECT_ID(''tempdb..#T'')
它可以用于键入系统表,并且内部生成的名称与CREATE TABLE
语句中定义的名称更密切相关。对于表变量,该object_id
函数不起作用,内部名称完全是系统生成的,与变量名称无关(名称是对象 id 的十六进制形式)。
下面演示了元数据仍然存在,但是通过键入(希望是唯一的)列名。DBCC PAGE
对于没有唯一列名的表,只要它们不为空,就可以使用 object_id 来确定它们。
/*Declare a table variable with some unusual options.*/DECLARE @T TABLE([dba.se] INT IDENTITY PRIMARY KEY NONCLUSTERED,A INT CHECK (A > 0),B INT DEFAULT 1,InRowFiller char(1000) DEFAULT REPLICATE(''A'',1000),OffRowFiller varchar(8000) DEFAULT REPLICATE(''B'',8000),LOBFiller varchar(max) DEFAULT REPLICATE(cast(''C'' as varchar(max)),10000),UNIQUE CLUSTERED (A,B) WITH (FILLFACTOR = 80, IGNORE_DUP_KEY = ON, DATA_COMPRESSION = PAGE, ALLOW_ROW_LOCKS=ON, ALLOW_PAGE_LOCKS=ON))INSERT INTO @T (A)VALUES (1),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13)SELECT t.object_id, t.name, p.rows, a.type_desc, a.total_pages, a.used_pages, a.data_pages, p.data_compression_descFROM tempdb.sys.partitions AS p INNER JOIN tempdb.sys.system_internals_allocation_units AS a ON p.hobt_id = a.container_id INNER JOIN tempdb.sys.tables AS t ON t.object_id = p.object_id INNER JOIN tempdb.sys.columns AS c ON c.object_id = p.object_idWHERE c.name = ''dba.se''
输出
Duplicate key was ignored.
object_id | 姓名 | 行 | type_desc | 总页数 | used_pages | 数据页 | data_compression_desc |
---|---|---|---|---|---|---|---|
574625090 | #22401542 | 13 | IN_ROW_DATA | 2 | 2 | 1 | 页 |
574625090 | #22401542 | 13 | LOB_DATA | 24 | 19 | 0 | 页 |
574625090 | #22401542 | 13 | ROW_OVERFLOW_DATA | 16 | 14 | 0 | 页 |
574625090 | #22401542 | 13 | IN_ROW_DATA | 2 | 2 | 1 | 没有任何 |
在 SQL Server 2012 之前,临时表和表变量的对象 ID 为正数。从 SQL Server 2012 开始,临时表和表变量的对象 ID 始终为负数(设置高位)。
交易
对表变量的操作作为系统事务执行,独立于任何外部用户事务,而等效的#temp
表操作将作为用户事务本身的一部分执行。由于这个原因,一个ROLLBACK
命令会影响一个#temp
表,但不会改变一个表变量。
DECLARE @T TABLE(X INT)CREATE TABLE #T(X INT)BEGIN TRANINSERT #TOUTPUT INSERTED.X INTO @TVALUES(1),(2),(3)/*Both have 3 rows*/SELECT * FROM #TSELECT * FROM @TROLLBACK/*Only table variable now has rows*/SELECT * FROM #TSELECT * FROM @TDROP TABLE #T
日志记录
tempdb
两者都为事务日志生成日志记录。一个常见的误解是表变量并非如此,因此下面的脚本演示了这一点,它声明了一个表变量,添加了几行,然后更新它们并删除它们。
因为表变量是在批处理开始和结束时隐式创建和删除的,所以需要使用多个批处理才能查看完整的日志记录。
USE tempdb;/*Don''t run this on a busy server.Ideally should be no concurrent activity at all*/CHECKPOINT;GO/*The 2nd column is binary to allow easier correlation with log output shown later*/DECLARE @T TABLE ([C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3] INT, B BINARY(10))INSERT INTO @TVALUES (1, 0x41414141414141414141), (2, 0x41414141414141414141)UPDATE @TSET B = 0x42424242424242424242DELETE FROM @T/*Put allocation_unit_id into CONTEXT_INFO to access in next batch*/DECLARE @allocId BIGINT, @Context_Info VARBINARY(128)SELECT @Context_Info = allocation_unit_id, @allocId = a.allocation_unit_id FROM sys.system_internals_allocation_units a INNER JOIN sys.partitions p ON p.hobt_id = a.container_id INNER JOIN sys.columns c ON c.object_id = p.object_idWHERE ( c.name = ''C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3'' )SET CONTEXT_INFO @Context_Info/*Check log for records related to modifications of table variable itself*/SELECT Operation, Context, AllocUnitName, [RowLog Contents 0], [Log Record Length]FROM fn_dblog(NULL, NULL)WHERE AllocUnitId = @allocIdGO/*Check total log usage including updates against system tables*/DECLARE @allocId BIGINT = CAST(CONTEXT_INFO() AS BINARY(8));WITH T AS (SELECT Operation, Context, CASE WHEN AllocUnitId = @allocId THEN ''Table Variable'' WHEN AllocUnitName LIKE ''sys.%'' THEN ''System Base Table'' ELSE AllocUnitName END AS AllocUnitName, [Log Record Length] FROM fn_dblog(NULL, NULL) AS D)SELECT Operation = CASE WHEN GROUPING(Operation) = 1 THEN ''Total'' ELSE Operation END, Context, AllocUnitName, [Size in Bytes] = COALESCE(SUM([Log Record Length]), 0), Cnt = COUNT(*)FROM TGROUP BY GROUPING SETS( ( Operation, Context, AllocUnitName ), ( ) )ORDER BY GROUPING(Operation), AllocUnitName
退货
详细视图
摘要视图(包括隐式删除和系统基表的日志记录)
据我所知,两者上的操作会产生大致相等的日志记录。
虽然日志记录的数量非常相似,但一个重要的区别是与#temp
表相关的日志记录在任何包含用户事务完成之前不能被清除,因此在某些时候写入#temp
表的长时间运行的事务将阻止日志截断,tempdb
而自治事务不会为表变量生成。
表变量不支持TRUNCATE
,因此当要求从表中删除所有行时,可能会处于日志记录的劣势(尽管对于非常小的表DELETE
无论如何都可以更好地工作
基数
许多涉及表变量的执行计划将显示单行估计为它们的输出。检查表变量属性表明 SQL Server 认为表变量有零行(Paul White 解释了为什么它估计会从零行表中发出一行)。
但是,上一节中显示的结果确实显示了准确的rows
计数sys.partitions
。问题是在大多数情况下,引用表变量的语句是在表为空时编译的。如果在填充表变量后(重新)编译语句,则当前行数将用于表基数(这可能是由于显式recompile
或可能因为语句还引用了另一个导致延迟编译的对象或重新编译。)
DECLARE @T TABLE(I INT);INSERT INTO @T VALUES(1),(2),(3),(4),(5)CREATE TABLE #T(I INT)/*Reference to #T means this statement is subject to deferred compile*/SELECT * FROM @T WHERE NOT EXISTS(SELECT * FROM #T)DROP TABLE #T
计划显示延迟编译后的准确估计行数。
在 SQL Server 2012 SP2 中,引入了跟踪标志 2453。更多详细信息在此处的“关系引擎”下。
当启用此跟踪标志时,它可能会导致自动重新编译以考虑更改的基数,正如稍后将进一步讨论的那样。
注意:在兼容级别 150 的 Azure 上,该语句的编译现在推迟到第一次执行。这意味着它将不再受到零行估计问题的影响。
无列统计
然而,拥有更准确的表基数并不意味着估计的行数会更准确(除非对表中的所有行进行操作)。SQL Server 根本不维护表变量的列统计信息,因此将依赖于基于比较谓词的猜测(例如,=
针对非唯一列将返回 10% 的表,或针对>
比较返回 30% 的表)。相反,为表维护列统计信息#temp
。
SQL Server 维护对每列所做的修改次数的计数。如果自编译计划以来的修改次数超过重新编译阈值 (RT),则将重新编译计划并更新统计信息。RT 取决于表类型和大小。
来自SQL Server 2008 中的计划缓存
RT 计算如下。(n 指编译查询计划时表的基数。)
常设桌
- 如果 n <= 500,则 RT = 500。
- 如果 n > 500,则 RT = 500 + 0.20 * n。
临时表
- 如果 n < 6,则 RT = 6。
- 如果 6 <= n <= 500,则 RT = 500。
- 如果 n > 500,则 RT = 500 + 0.20 * n。
表变量- RT 不存在。因此,不会因为表变量的基数变化而发生重新编译。 (但请参阅下面关于 TF 2453 的说明)
该KEEP PLAN
提示可用于将表的 RT 设置为#temp
与永久表相同。
所有这一切的最终结果是,#temp
当涉及许多行时,为表生成的执行计划通常比为表变量生成的执行计划好几个数量级,因为 SQL Server 有更好的信息可以使用。
NB1:表变量没有统计信息,但仍会在跟踪标志 2453 下引发“统计信息已更改”重新编译事件(不适用于“琐碎”计划)这似乎发生在与上述临时表相同的重新编译阈值下额外的一个 if N=0 -> RT = 1
。即当表变量为空时编译的所有语句最终将在非空时第一次执行时重新编译并更正TableCardinality 。编译时间表基数存储在计划中,如果语句以相同的基数再次执行(由于控制语句的流或缓存计划的重用),则不会发生重新编译。
NB2:对于存储过程中的缓存临时表,重新编译的故事比上面描述的要复杂得多。有关所有详细信息,请参阅存储过程中的临时表。
重新编译
除了上面描述的基于修改的重新编译之外,#temp
表还可以与其他编译相关联,因为它们允许对触发编译的表变量进行禁止的操作(例如 DDL 更改CREATE INDEX
,ALTER TABLE
)
锁定
已经声明表变量不参与锁定。不是这种情况。将以下输出运行到 SSMS 消息选项卡,以获取为插入语句获取和释放的锁的详细信息。
DECLARE @tv_target TABLE (c11 int, c22 char(100))DBCC TRACEON(1200,-1,3604)INSERT INTO @tv_target (c11, c22)VALUES (1, REPLICATE(''A'',100)), (2, REPLICATE(''A'',100))DBCC TRACEOFF(1200,-1,3604)
对于SELECT
来自表变量的查询,Paul White 在注释中指出这些自动带有隐式NOLOCK
提示。如下所示
DECLARE @T TABLE(X INT); SELECT XFROM @T OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8607)
输出
*** Output Tree: (trivial plan) *** PhyOp_TableScan TBL: @T Bmk ( Bmk1000) IsRow: COL: IsBaseRow1002 Hints( NOLOCK )
然而,这对锁定的影响可能很小。
SET NOCOUNT ON;CREATE TABLE #T( [ID] [int] IDENTITY NOT NULL, [Filler] [char](8000) NULL, PRIMARY KEY CLUSTERED ([ID] DESC)) DECLARE @T TABLE ( [ID] [int] IDENTITY NOT NULL, [Filler] [char](8000) NULL, PRIMARY KEY CLUSTERED ([ID] DESC))DECLARE @I INT = 0WHILE (@I < 10000)BEGININSERT INTO #T DEFAULT VALUESINSERT INTO @T DEFAULT VALUESSET @I += 1END/*Run once so compilation output doesn''t appear in lock output*/EXEC(''SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T'')DBCC TRACEON(1200,3604,-1)SELECT *, sys.fn_PhysLocFormatter(%%physloc%%)FROM @TPRINT ''--*--''EXEC(''SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T'')DBCC TRACEOFF(1200,3604,-1)DROP TABLE #T
这些返回结果都不是索引键顺序,表明 SQL Server 对两者都使用了分配顺序扫描。
我运行了上述脚本两次,第二次运行的结果如下
Process 58 acquiring Sch-S lock on OBJECT: 2:-1325894110:0 (class bit0 ref1) result: OK--*--Process 58 acquiring IS lock on OBJECT: 2:-1293893996:0 (class bit0 ref1) result: OKProcess 58 acquiring S lock on OBJECT: 2:-1293893996:0 (class bit0 ref1) result: OKProcess 58 releasing lock on OBJECT: 2:-1293893996:0
表变量的锁定输出确实非常少,因为 SQL Server 只是在对象上获取了架构稳定性锁。但是对于一个#temp
表来说,它几乎和它一样轻,因为它取出了一个对象级S
锁。当然,在使用表时也可以显式指定NOLOCK
提示或隔离级别。READ UNCOMMITTED``#temp
与记录周围用户事务的问题类似,这可能意味着表的锁定时间更长#temp
。使用下面的脚本
--BEGIN TRAN; CREATE TABLE #T (X INT,Y CHAR(4000) NULL); INSERT INTO #T (X) VALUES(1) SELECT CASE resource_type WHEN ''OBJECT'' THEN OBJECT_NAME(resource_associated_entity_id, 2) WHEN ''ALLOCATION_UNIT'' THEN (SELECT OBJECT_NAME(object_id, 2) FROM tempdb.sys.allocation_units a JOIN tempdb.sys.partitions p ON a.container_id = p.hobt_id WHERE a.allocation_unit_id = resource_associated_entity_id) WHEN ''DATABASE'' THEN DB_NAME(resource_database_id) ELSE (SELECT OBJECT_NAME(object_id, 2) FROM tempdb.sys.partitions WHERE partition_id = resource_associated_entity_id) END AS object_name, * FROM sys.dm_tran_locks WHERE request_session_id = @@SPID DROP TABLE #T -- ROLLBACK
对于这两种情况,当在显式用户事务之外运行时,检查时返回的唯一锁sys.dm_tran_locks
是DATABASE
.
取消注释BEGIN TRAN ... ROLLBACK
时返回 26 行,表明对象本身和系统表行都持有锁,以允许回滚并防止其他事务读取未提交的数据。等效的表变量操作不受用户事务回滚的影响,并且不需要为我们在下一条语句中检查而持有这些锁,但是跟踪在 Profiler 中获取和释放的锁或使用跟踪标志 1200 显示大量锁定事件仍然存在发生。
索引
对于 SQL Server 2014 之前的版本,索引只能在表变量上隐式创建,这是添加唯一约束或主键的副作用。这当然意味着只支持唯一索引。UNIQUE NONCLUSTERED
可以通过简单地声明它并将 CI 键添加到所需 NCI 键的末尾来模拟具有唯一聚集索引的表上的非唯一非聚集索引(SQL Server无论如何都会在幕后执行此操作,即使非唯一NCI 可以指定)
如前所述index_option
,可以在约束声明中指定各种 s ,包括DATA_COMPRESSION
, IGNORE_DUP_KEY
, and FILLFACTOR
(尽管设置它没有意义,因为它只会对索引重建产生任何影响,并且您不能重建表变量上的索引!)
此外,表变量不支持INCLUDE
d 列、过滤索引(直到 2016 年)或分区,#temp
表支持(分区方案必须在 中创建tempdb
)。
SQL Server 2014 中的索引
可以在 SQL Server 2014 的表变量定义中内联声明非唯一索引。示例语法如下。
DECLARE @T TABLE (C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/C2 INT INDEX IX2 NONCLUSTERED, INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/);
SQL Server 2016 中的索引
从 CTP 3.1 开始,现在可以为表变量声明过滤索引。通过 RTM,可能会允许包含的列,尽管由于资源限制它们可能不会进入 SQL16
DECLARE @T TABLE(c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/)
并行性
插入(或以其他方式修改)表变量的查询不能有并行计划,#temp
表不受这种方式的限制。
有一个明显的解决方法,如下重写确实允许SELECT
部分并行发生,但最终使用隐藏的临时表(在幕后)
INSERT INTO @DATA ( ... ) EXEC(''SELECT .. FROM ...'')
其他功能差异
#temp_tables
不能在函数内部使用。表变量可以在标量或多语句表 UDF 中使用。- 表变量不能有命名约束。
- 表变量不能是
SELECT
-edINTO
、ALTER
-ed 、TRUNCATE
d 或作为or of等DBCC
命令的目标,并且不支持表提示,例如DBCC CHECKIDENT``SET IDENTITY INSERT``WITH (FORCESCAN)
CHECK
为了简化、隐含谓词或矛盾检测,优化器不考虑对表变量的约束。- 表变量不符合行集共享优化的条件,这意味着针对这些变量的删除和更新计划可能会遇到更多开销和
PAGELATCH_EX
等待。(例)
仅记忆?
如开头所述,两者都存储在tempdb的页面上。但是,在将这些页面写入持久存储时,我没有说明在行为上是否存在任何差异。
我现在已经对此进行了少量测试,到目前为止还没有看到这种差异。在我对 SQL Server 250 页面实例进行的特定测试中,似乎是数据文件被写入之前的截止点。
注意:在 SQL Server 2014 或SQL Server 2012 SP1/CU10 或 SP2/CU1中不再出现以下行为,急切的编写器不再急切地刷新页面。有关SQL Server 2014更改的更多详细信息: tempdb 隐藏的性能宝石。
运行以下脚本
CREATE TABLE #T(X INT, Filler char(8000) NULL)INSERT INTO #T(X)SELECT TOP 250 ROW_NUMBER() OVER (ORDER BY @@SPID)FROM master..spt_valuesDROP TABLE #T
使用 Process Monitor 监控对tempdb数据文件的写入,我没有看到任何内容(除了偶尔在偏移 73,728 处的数据库引导页面)。将 250 更改为 251 后,我开始看到如下写入。
上面的屏幕截图显示了 5 * 32 页写入和一个单页写入,表示已写入 161 页。在使用表变量进行测试时,我也得到了 250 页的相同截止点。下面的脚本通过查看以不同的方式显示它sys.dm_os_buffer_descriptors
DECLARE @T TABLE ( X INT, [dba.se] CHAR(8000) NULL)INSERT INTO @T (X)SELECT TOP 251 Row_number() OVER (ORDER BY (SELECT 0))FROM master..spt_valuesSELECT is_modified, Count(*) AS page_countFROM sys.dm_os_buffer_descriptorsWHERE database_id = 2 AND allocation_unit_id = (SELECT a.allocation_unit_id FROM tempdb.sys.partitions AS p INNER JOIN tempdb.sys.system_internals_allocation_units AS a ON p.hobt_id = a.container_id INNER JOIN tempdb.sys.columns AS c ON c.object_id = p.object_id WHERE c.name = ''dba.se'')GROUP BY is_modified
结果
is_modified | page_count |
---|---|
0 | 192 |
1 | 61 |
显示已写入 192 页并清除了脏标志。它还表明,被写入并不意味着页面将立即从缓冲池中逐出。仍然可以完全从内存中满足针对此表变量的查询。
在max server memory
设置为 2000 MB 并DBCC MEMORYSTATUS
报告 Buffer Pool Pages Allocated 为大约 1,843,000 KB(c. 23,000 页)的空闲服务器上,我以 1,000 行/页的批次插入到上面的表中,并记录了每次迭代。
SELECT Count(*)FROM sys.dm_os_buffer_descriptorsWHERE database_id = 2 AND allocation_unit_id = @allocId AND page_type = ''DATA_PAGE''
表变量和#temp
表都给出了几乎相同的图表,并且在达到它们没有完全保存在内存中之前设法几乎最大化缓冲池,因此似乎对多少内存没有任何特别的限制两者都可以消费。
SQL Server 临时表与表变量的区别分析
感兴趣的小伙伴,下面一起跟随小编 jb51.cc的小编两巴掌来看看吧!
在实际使用的时候,我们如何灵活的在存储过程中运用它们,虽然它们实现的功能基本上是一样的,可如何在一个存储过程中有时候去使用临时表而不使用表变量,有时候去使用表变量而不使用临时表呢?
临时表
临时表与永久表相似,只是它的创建是在Tempdb中,它只有在一个数据库连接结束后或者由sql命令DROP掉,才会消失,否则就会一直存在。临时表在创建的时候都会产生sql Server的系统日志,虽它们在Tempdb中体现,是分配在内存中的,它们也支持物理的磁盘,但用户在指定的磁盘里看不到文件。
临时表分为本地和全局两种,本地临时表的名称都是以“#”为前缀,只有在本地当前的用户连接中才是可见的,当用户从实例断开连接时被删除。全局临时表的名称都是以“##”为前缀,创建后对任何用户都是可见的,当所有引用该表的用户断开连接时被删除。
下面我们来看一个创建临时表的例子:
代码如下:
CREATE TABLE dbo.#News
(
News_id int NOT NULL, NewsTitle varchar(100), NewsContent varchar(2000), NewsDateTime datetime
)
临时表可以创建索引,也可以定义统计数据,所以可以用数据定义语言(DDL)的声明来阻止临时表添加的限制,约束,并参照完整性,如主键和外键约束。比如来说,我们现在来为#News表字段NewsDateTime来添加一个默认的GetData()当前日期值,并且为News_id添加一个主键,我们就可以使用下面的语句:
代码如下:
ALTER TABLE dbo.#News
ADD
CONSTRAINT [DF_NewsDateTime] DEFAULT (GETDATE()) FOR [NewsDateTime], PRIMARY KEY CLUSTERED
(
[News_id]
) ON [PRIMARY]
GO
临时表在创建之后可以修改许多已定义的选项,包括:
1)添加、修改、删除列。例如,列的名称、长度、数据类型、精度、小数位数以及为空性均可进行修改,只是有一些限制而已。
2)可添加或删除主键和外键约束。
3)可添加或删除 UNIQUE 和 CHECK 约束及 DEFAULT 定义(对象)。
4)可使用 IDENTITY 或 ROWGUIDCOL 属性添加或删除标识符列。虽然 ROWGUIDCOL 属性也可添加至现有列或从现有列删除,但是任何时候在表中只能有一列可具有该属性。
5)表及表中所选定的列已注册为全文索引。
表变量
表变量创建的语法类似于临时表,区别就在于创建的时候,必须要为之命名。表变量是变量的一种,表变量也分为本地及全局的两种,本地表变量的名称都是以“@”为前缀,只有在本地当前的用户连接中才可以访问。全局的表变量的名称都是以“@@”为前缀,一般都是系统的全局变量,像我们常用到的,如 @@Error代表错误的号,@@RowCount代表影响的行数。
如我们看看创建表变量的语句:
代码如下:
DECLARE @News Table
(
News_id int NOT NULL, NewsDateTime datetime
)
比较临时表及表变量都可以通过sql的选择、插入、更新及删除语句,它们的的不同主要体现在以下这些:
1)表变量是存储在内存中的,当用户在访问表变量的时候,sql Server是不产生日志的,而在临时表中是产生日志的;
2)在表变量中,是不允许有非聚集索引的;
3)表变量是不允许有DEFAULT默认值,也不允许有约束;
4)临时表上的统计信息是健全而可靠的,但是表变量上的统计信息是不可靠的;
5)临时表中是有锁的机制,而表变量中就没有锁的机制。
我们现在来看一个完整的例子,来看它们的用法的异同:
利用临时表
代码如下:
CREATE TABLE dbo.#News
(
News_id int NOT NULL, NewsDateTime datetime
)
INSERT INTO dbo.#News (News_id,NewsTitle,NewsContent,NewsDateTime)
VALUES (1,'BlueGreen','Austen',200801,GETDATE())
SELECT News_id,NewsDateTime FROM dbo.#News
DROP TABLE dbo.[#News]
利用表变量
代码如下:
DECLARE @News table
(
News_id int NOT NULL, NewsDateTime datetime
)
INSERT INTO @News (News_id,NewsDateTime FROM @News
我们可以看到上面两种情况实现的是一样的效果,第一种利用临时表的时候,临时表一般被创建后,如果在执行的时候,没有通过DROP Table的操作,第二次就不能再被创建,而定义表变量也不需要进行DROP Table的操作,一次执行完成后就会消失。
其实在选择临时表还是表变量的时候,我们大多数情况下在使用的时候都是可以的,但一般我们需要遵循下面这个情况,选择对应的方式:
1)使用表变量主要需要考虑的就是应用程序对内存的压力,如果代码的运行实例很多,就要特别注意内存变量对内存的消耗。我们对于较小的数据或者是通过计算出来的推荐使用表变量。如果数据的结果比较大,在代码中用于临时计算,在选取的时候没有什么分组的聚合,就可以考虑使用表变量。
2)一般对于大的数据结果,或者因为统计出来的数据为了便于更好的优化,我们就推荐使用临时表,同时还可以创建索引,由于临时表是存放在Tempdb中,一般默认分配的空间很少,需要对tempdb进行调优,增大其存储的空间。
3)如果要在自定义函数中返回一个表,要用表变量如:
代码如下:
dbo.usp_customersbyPostalCode
( @PostalCode VARCHAR(15) )
RETURNS
@CustomerHitsTab TABLE (
[CustomerID] [nchar] (5),[ContactName] [nvarchar] (30),[Phone] [nvarchar] (24),[Fax] [nvarchar] (24)
)
AS
BEGIN
DECLARE @HitCount INT
INSERT INTO @CustomerHitsTab
SELECT [CustomerID],[ContactName],[Phone],[Fax]
FROM [northwind].[dbo].[Customers]
WHERE PostalCode = @PostalCode
SELECT @HitCount = COUNT(*)
FROM @CustomerHitsTab
IF @HitCount = 0
--No Records Match Criteria
INSERT INTO @CustomerHitsTab (
[CustomerID],[Fax] )
VALUES ('','No Companies In Area','','')
RETURN
END
GO
【图片暂缺】
sql server 表变量、表类型、临时表
sql server 中临时表分为会话临时表和永久临时表。会话临时表在会话结束后自动被删除,永久临时表与基本表的使用上基本无差异,需要显示调用drop将其删除。
创建临时表
创建会话临时表
create table #table_name(column_name datatype constraint_name[.....]);
创建永久临时表
create table ##table_name(column_name datatype constraint_name[.....]);
临时表的约束可以在创建表之后建立,使用
alter table table_name add constraint contraint_name;语句创建。
表类型
表类型是一个用户自定义类型,用户可以创建自己所需要的表类型,说白了就是把表结构和约束预先创建好,后面要使用的时候直接根据该表类型创建表变量。
创建表类型
create type type_name as table(column_name datatype constraint_name[.....]);
在这里表中的约束必须在创建类型的时候就创建,不能使用alter语句。
根据表类型申明表变量
declare @table_variable type_name;
表变量
表变量是一种数据类型,该类型具有表的结构和部分表的功能。可以对其进行查询、插入、更新、删除。值得注意的是表变量不能使用select .. into语句插入数据
但是临时表可以。表变量可以结合存储过程、函数等程序块使用。表变量与其他基本变量类型的方法和申明一致。
申明表变量
declare @table_variable table(column_name datatype constraint_name[.....]);
我们今天的关于SQL Server中的临时表和表变量和sqlserver临时表详解的分享已经告一段落,感谢您的关注,如果您想了解更多关于SQL Server 2008中的临时表上的sql-server – nolock、SQL Server 中的临时表和表变量有什么区别?、SQL Server 临时表与表变量的区别分析、sql server 表变量、表类型、临时表的相关信息,请在本站查询。
本文标签: