9.3 Tempdb的并发阻塞
在介绍Tempdb的并发问题前,先介绍几个比较特殊的数据页。
PFS(Page Free Space),用于标识数据页空间的使用情况,以字节标识,可以表示数据页使用百分比,例如使用百分五十,百分八十,百分九十五以及完全被使用,同时,还有一个字节位表示数据页的类型,例如IAM页等。一个PFS页,可以标识64MB的数据页空间使用情况。
GAM(Global Allocation Map),用于标识数据盘区(Extent)是否已分配,以位标识,当位为0时,表示盘区还未分配,当位为1时,表示盘区已经分配。一个GAM页可以标识大约4GB的数文件空间标识。
SGAM(Shared Global Allocation Map),用于标识混合数据盘区是否已分配,以位标识。混合空间中可能被存储数据,也可能存储索引。一个SGAM页可以标识大约4GB的数据文件混空间标识。
Tempdb中,临时表的数据特性如前面章节中介绍的,当会话中创建临时表并初始化时,分配数据空间,在会话中删除临时表,或会话结束,便会将空间回收。数据的新增和删除,都需要使用到数据页,当数据页的存储空间大小发生变化时,PFS的字节标识值就需要发生变化。当这样的操作频率地发生在相同的一块数据页空间区域内时。如前面讲闩锁时所知道的,内在页在更新时,是需要使用闩锁来实现互斥的,当并发操作累积到特定的数值时,将会频繁地出现闩锁等待,此时有可能会造成服务器CPU使用率上升,甚至发生CPU满负荷的情况。
出于这样的原因,通常建议将Tempdb的数据文件个数增加到与逻辑CPU个数相同。这样可以达到数据分流的目的,避免在相同的数据区域内频繁地擦写数据。
相同的情况也有可能发生在GAM和SGAM页上,只是GAM和SGAM要求临时表的数据量较大。
除了PFS,GAM以及SGAM几个root页的并发阻塞问题外,Tempdb还可能因为频繁地创建临时表而发生系统表阻塞的问题。例如,在高并发访问的某个存储过程中,使用SELECT INTO的方式创建临时表,当并发访问足够高,或者查询语句需要消耗的时间比较长且有一定的并发量时,可能导致系统的元数据表,比如sys.objects视图对应的系统元数据表sysschobjs或者sys.columns视图对应的这些系统元数据表syscolpars,这些系统元数据也有可能由于高并发以及较长的事务而产生大量的系统阻塞的情况。解决这样问题的方式便是,创建临时表时,不应该将创建临时表的语句包含在较长的事务当中。避免Tempdb的系统表被锁住,引起别的进程无法正常创建或操作临时表。
另外,在更高并发的情况下,为了达到Tempdb有更好的数据吞吐能力,建议将数据文件放在不同的磁盘上,以提高磁盘的吞吐量。
接下来,我们会使用到一个测试工具,工具名为ostress。是微软提供的SQL Server测试工具组件RML Utilities For SQL Server中的一个,大家可以到微软官网下载该工具,这个工具组件是免费的。
Tempdb并发示例
以下链接为64位工具下载地址:
http://www.microsoft.com/en-us/download/details.aspx?id=4511
以下是32位工具的下载地址:
http://www.microsoft.com/en-us/download/details.aspx?id=8161
下载完测试软件,并安装后,我们需要在数据库中,创建相应的存储过程,存储过程代码如代码清单9-5中所示,创建两个存储过程,usp_temp_table_test作为子存储过程,usp_loop_test_table_test循环调用它。
CREATE PROC dbo.usp_temp_table_test
AS
BEGIN
????CREATE TABLE #table(c1 INT,c2 CHAR(5000));
????DECLARE @i INT=1;
????WHILE(@i<=10)
????BEGIN
????????INSERT INTO #table (c1,c2)VALUES (@i,‘test‘);
????????SET @i+=1;
????END
END
GO
?
CREATE PROC dbo.usp_loop_temp_table_test
AS
BEGIN
????SET NOCOUNT ON;
????DECLARE @i INT=1;
????WHILE(@i<100)
????BEGIN
????????EXEC dbo.usp_temp_table_test;
????????SET @i+=1;
????END
END
GO
代码清单9-5 Tempdb并发阻塞测试
创建对应的代码后,使用ostress模拟用户并发的情况,执行下面的Windows批处理指令,如代码清单9-6中所示,使用ostress模拟300个并发执行我们创建的存储过程。
"C:\Program Files\Microsoft Corporation\RMLUtils\ostress.exe" -S(local)\SQL2012 -E -Q"exec [AdvantureWorks2008R2].dbo.usp_loop_temp_table_test;" -o"D:\output.txt" -n300
代码清单9-6 执行ostress工具
在执行ostress时,使用动态管理视图查看当前服务器内存中,Tempdb内存页的闩锁等待情况,全县下面的语句,查询动态管理视图sys.dm_os_waiting_tasks获取相应的阻塞信息,代码如代码清单9-7中所示。
;WITH waiting_tasks
AS (SELECT session_id,
???????????????????? wait_type,
???????????????????? wait_duration_ms,
???????????????????? blocking_session_id,
???????????????????? resource_description,
PageID = CONVERT(INT,RIGHT(resource_description,LEN(resource_description)-CHARINDEX(‘:‘,resource_description,3)))
FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE ‘PAGE%LATCH_%‘ AND resource_description LIKE ‘2:%‘)
SELECT session_id,
???? wait_type,
???? wait_duration_ms,
???? blocking_session_id,
???? resource_description,
ResourceType = CASE WHEN PageID=1 OR PageID%8088=0 THEN ‘PFS Page‘
WHEN PageID=2 OR PageID%511232=0 THEN ‘GAM Page‘
WHEN PageID=3 OR (PageID-1)%511232=0 THEN ‘SGAM Page‘
ELSE ‘Not PFS, GAM, or SGAM page‘
END
FROM waiting_tasks;
代码清单9-7 查看当前tempdb的Latch情况
通过查询语句,可以看到如图9-5中所示,此时服务器绝大部分是PFS数据页的闩锁等待。
图9-5 服务器的闩锁等待
从执行结果上看到,300个并发任务中,有297个正在等待数据页2:1:1,此处页为PFS数据页,其等待类型为PAGELATCH_UP类型。导致这样的原因这是因为Tempdb在实例中只有一个数据文件,高并发的情况下,导致数据页并发更新带来的阻塞。
9.3.1 配置Tempdb
如何配置Tempdb可以避免图9-5中的问题,在前面小节中我们已经有提到过了,提高并发的方法便是增加数据文件,将并发分开,避免对同一个文件进行操作。那么配置多个文件需要注意些什么事项呢?
SQL Server对于具有多个文件的数据库在写入数据时,数据库引擎将按照各个数据文件的大小比例进行数据分配,因此,当对Tempdb配置多个文件时,要保持Tempdb的数据文件大小保持一致,这样才能达到在存储Tempdb数据时,可以达到近乎平均使用和分配数据文件的作用,才能达到平均使用每个文件中的PFS,GAM以及SGAM这些root数据页的目的。