Buffer Latch Timeout的解析
【问题描述】
我们可能会在数据库的错误日志里,发现这么一条信息:
A time-out occurred while waiting for buffer latch -- type 4, bp 000000097BFDEDC0, page 1:19239, stat 0xc00009, database id: 5, allocation unit Id: 72057615247867904, task 0x0000000005E594C8 : 0, waittime 300, flags 0x1018, owning task 0x0000000000169DC8. Not continuing to wait.
我们的问题是,这个错误到底是什么含义,在什么情况下会报上面的错误,以及如何解决?
【背景介绍】
Latch是SQL Server内部用来同步资源访问的一个数据结构。和操作系统的Critical Section或ReaderWriterLock类似。Latch保护了那些想保护的资源,使得访问同步有序。比方说,当某个线程获得某个资源的Latch的独占使用权时候,别的线程如果也需要访问这个Latch,则它必须等待。
从大的方面来讲,有两种Latch, 一种叫Buffer Latch,另外一种叫I/O Latch.
我们先来简短介绍一下I/O Latch。当SQL Server从硬盘上读取一个页时,会先在内存预留该页的空间。并且在该预留空间的某一个位BUF_IO设为1. 如果数据从硬盘读写完成,则该位设为0. 从硬盘读取页的期间,其他也需要访问该页的线程当然要等待,等待类型为PAGEIOLATCH_SH,直到读写完成,BUF_IO被设为0为止。因此,如果我们看到大量PAGEIOLATCH_SH等待,则基本可以断定问题是出在磁盘性能上面。
另外一种Latch则称为Buffer Latch,用来保护内存里的数据结构,如Index, Data Pages, B树中的Non-Leaf页。当进程需要读取一个内存里的数据页时,该进程要先获取该数据页上的Buffer Latch。有各种类型的Latch, 包括独占Latch(PAGELATCH_EX)和共享Latch(PAGELATCH_SH)。
下面来演示,为什么我们需要Latch。如下图所示,我们在页面100上,已经存放了两条记录。
如果没有Latch锁的话,某进程在页面100上,插入如下数据:INSERT VALUES(3, 300),其结果如下:
这时,另外一个进程要在页面100上,插入如下数据: INSERT VALUES(4, 400), 因为没有Latch锁,所以会覆盖之前的数据。导致数据插入出问题。
正确的做法是,我们要在第一个线程进行操作时,加独占Latch锁。第二个线程必须要等待,直到第一个线程操作完成。如下图所示:
开始第一条记录插入,随后修改m_freedata(值为141)以及Row的指针(值为126),在此期间,第二条插入语句处于等待Latch状态。第一条记录插入完成后,释放独占Latch锁。
第二条记录开始插入,插入期间也会加独占Latch锁,以防止其他进程修改或读取页。完成后,也随即释放Latch锁。最后结果如图所示。由于有Latch锁,所以数据的插入可以有序的进行。
【LATCH申请模式】
Latch在申请的时候有以下几种模式,
- KP – Keep Latch 保证引用的结构不能被破坏
- SH – Shared Latch, 读数据页的时候需要
- UP – Update Latch 更改数据页的时候需要
- EX – Exclusive Latch 独占模式,主要用于写数据页的时候需要
- DT – Destroy Latch 在破坏引用的数据结构时所需要
下表显示各种Latch申请的兼容模式:
Y表明是兼容的,如果两个线程都去读某页,则他们都会去申请SH锁,因为SH锁是兼容的,则两个线程都不会互相妨碍。而N表明是不兼容的,必须要等待。直到前面一个Latch被释放为止。
【LATCH等待类型】
Latch的等待主要有三种。
- Buffer (BUF) Latch. 用来保护索引或数据页。也包括PFS, GAM, SGAM和IAM数据页。等待类型是PAGELATCH_*模式
- Non-buffer(Non-BUF) Latch, 除了上述数据结构以外的其他内存结构。等待类型是LATCH_*模式
- IO Latch, 保护数据从磁盘到页面的读写过程,等待类型是PAGEIOLATCH_*模式
我们可以查询下面的语句了解一下具体的等待种类:
SELECT * FROM sys.dm_os_wait_stats WHERE wait_type like '%Latch%'
【信息解读】
下面我们来解读这段信息。
A time-out occurred while waiting for buffer latch -- type 4, bp 000000097BFDEDC0, page 1:19239, stat 0xc00009, database id: 5, allocation unit Id: 72057615247867904, task 0x0000000005E594C8 : 0, waittime 300, flags 0x1018, owning task 0x0000000000169DC8. Not continuing to wait.
这里,type 4 是等待的类型。通过下面的查询,我们可以知道, type为4的Latch是EX模式。
SELECT * FROM sys.dm_xe_map_values where name = 'latch_mode'
bp 则是BUF结构的指针,如果没有内存转储,我们没有办法通过这个指针获取更有用的信息。Page (1:19239)则是等待的页编号。Stat 0xc00009则是BUF结构的位信息。其中的0x000004位则是我们上面提到的BUF_IO位。0xc00009 & 0x000004 = 0, 说明该页已经完全导入到内存里的BUF结构里了。Allocation unit id则是相对应的分配单元。一个数据库的表可以有多个分配单元,通常分配单元的个数和这个表的索引个数相同。
使用以下一些列查询,我们可以通过Allocation Unit ID得到其对应的表名。(当然我们也可以通过Page的具体信息来获得表名。用DBCC PAGE命令)
--结果中的container_id就是partition_id。
SELECT * FROM sys.allocation_units where allocation_unit_id=’72057615247867904’
--得到object_id
SELECT * FROM sys.partitions where partition_id = xxxx
--得到name
SELECT * FROM sys.objects where object_id = yyyy
Waittime为300,单位是秒,也就是说,我们申请在页(1:19239)上的一个独占的Latch锁,但是申请了300秒(5分钟),还是没申请到。
这里flags用来判断该latch是否是superLatch,对于分析latch等待用处不大,所以我们不进一步具体分析。
Task地址和owning task地址在内存转储分析中,非常重要。尤其是owning task, 是这个task阻塞了别的进程获得Latch锁。
【如何解决】
Latch申请不到,是由于被其他进程占用的缘故。所以我们需要找到哪个进程占用该Latch一直不释放。然后采取相应的办法进行解决。通常来讲,都是由于磁盘性能所造成的,如果需要得知确切的原因,我们可能需要对内存转储进行分析。对于第一个Latch Timeout的错误,SQL Server会产生一个内存转储。而对于后续的Latch Timeout,则会在SQL Server的错误日志里,报告一个错误信息,但不会产生内存转储。如果要改变这个行为,即要求每次碰到Latch Timeout,都要生成一个内存转储,我们可以用traceflag 838,如下面的命令:
dbcc traceon(838, -1)
由于生成内存转储需要一点时间,有可能在生成内存转储的时候,Latch等待现象消失,在内存转储中,没有足够的信息。因此,我们可能要多次抓取内存转储,以对问题进行分析。
【案例分析】
下面是一个案例分析。我们在数据库的错误日志里,发现有下面的错误,并且产生了一个内存转储。
A time-out occurred while waiting for buffer latch -- type 2, bp 0000000088FBFA40, page 1:153568, stat 0xc0010b, database id: 10, allocation unit Id: 72057594047758336, task 0x000000000C8E2988 : 0, waittime 300, flags 0x1a, owning task 0x0000000005A2F048. Not continuing to wait.
下面是对内存转储的的分析,为了简单起见,不相关的调用堆栈被省略。我们可以看到,下面的这个进程在获取Buffer Latch的时候,由于无法及时得到Latch锁,产生了上述错误信息,并生成一个内存转储。
0a 00000026`ebe09350 00000000`0215d1b8 sqlservr!CDmpDump::Dump+0x7c
0b 00000026`ebe093a0 00000000`0215dd0c sqlservr!SQLDumperLibraryInvoke+0x1a0
0c 00000026`ebe093d0 00000000`021503f7 sqlservr!CImageHelper::DoMiniDump+0x3d4
0d 00000026`ebe095d0 00000000`0206af82 sqlservr!stackTrace+0x82b
0e 00000026`ebe0ab20 00000000`02069f69 sqlservr!LatchBase::DumpOnTimeoutIfNeeded+0x19a
0f 00000026`ebe0abf0 00000000`008e7bcc sqlservr!LatchBase::PrintWarning+0x205
10 00000026`ebe0ace0 00000000`000e7a29 sqlservr!LatchBase::Suspend+0xd12
11 00000026`ebe0b950 00000000`000a0689 sqlservr!LatchBase::AcquireInternal+0x1ff
12 00000026`ebe0b9f0 00000000`000a1792 sqlservr!BUF::AcquireLatch+0x8d
13 00000026`ebe0bd10 00000000`000a166c sqlservr!BPool::Get+0xc7
14 00000026`ebe0bd70 00000000`000a35a0 sqlservr!PageRef::Fix+0xbc
15 00000026`ebe0bdd0 00000000`000a320a sqlservr!BTreeMgr::Seek+0x44d
16 00000026`ebe0bff0 00000000`000a2e5d sqlservr!BTreeMgr::GetHPageIdWithKey+0x20a
17 00000026`ebe0c070 00000000`000a38e8 sqlservr!IndexPageManager::GetPageWithKey+0xbd
这时,错误信息中的owning task地址0x0000000005A2F048对于问题的分析就很重要了。我们通过owning task的地址,结合内存转储分析,可以找到对应于该owning task的进程编号。其调用堆栈如下:
0:175> kM
# Child-SP RetAddr Call Site
00 00000026`f027fa68 000007fe`fcd910dc ntdll!ZwWaitForSingleObject+0xa
01 00000026`f027fa70 00000000`00083b1a KERNELBASE!WaitForSingleObjectEx+0x79
02 00000026`f027fb10 00000000`00082d76 sqlservr!SOS_Scheduler::SwitchContext+0x26d
03 00000026`f027ffa0 00000000`00082700 sqlservr!SOS_Scheduler::SuspendNonPreemptive+0xca
04 00000026`f027ffe0 00000000`000829ac sqlservr!SOS_Scheduler::Suspend+0x2d
05 00000026`f0280010 00000000`000e7e46 sqlservr!EventInternal<Spinlock<153,1,0> >::Wait+0x1a8
06 00000026`f0280060 00000000`000e7a29 sqlservr!LatchBase::Suspend+0x599
07 00000026`f0280cd0 00000000`00645aac sqlservr!LatchBase::AcquireInternal+0x1ff
08 00000026`f0280d70 00000000`00646a49 sqlservr!SQLServerLogMgr::GrowAFile+0x7c
09 00000026`f0281150 00000000`000cb38b sqlservr!SQLServerLogMgr::ReserveAndAppend+0x1fb
0a 00000026`f0281290 00000000`000cbe89 sqlservr!XdesRMReadWrite::GenerateLogRec+0x567
0b 00000026`f02813d0 00000000`000cbc23 sqlservr!XdesRMReadWrite::LogBeginXact+0x243
0c 00000026`f0281530 00000000`000cbc3f sqlservr!XdesRMReadWrite::MakeActive+0x64
0d 00000026`f0281560 00000000`001746c1 sqlservr!XdesRMReadWrite::GenerateLogRec+0x87
0e 00000026`f02816a0 00000000`00174860 sqlservr!PageRef::ModifyRow+0xce0
0f 00000026`f02818f0 00000000`00176749 sqlservr!PageRef::ModifyColumnsInternal+0x1b3
10 00000026`f0281b50 00000000`00175f71 sqlservr!IndexPageRef::Modify+0x2fd3
上面的调用堆栈显示,这是在增长数据库日志文件。由于数据库日志无法及时增长完成,因此导致了这个线程长时间拥有Latch锁而不释放,而其他进程无法及时获取相应的Latch锁。但查看该日志文件的增长设定,每次增长为300M。并不是以百分比增长。似乎没有什么大的问题。
进一步查看数据库的错误日志,在发生该警告之前,SQL Server已经有相关的磁盘性能警告:
SQL Server has encountered 18 occurrence(s) of I/O requests taking longer than 15 seconds to complete on file [F:\sqldata\logs\xxxx_log.ldf] in database [xxxx] (10). The OS file handle is 0x0000000000000A2C. The offset of the latest long I/O is: 0x000004e6511e00
通过收集磁盘性能信息,我们发现,在出问题的点上,Avg. Disk sec/Read达到了1.444. 通常这个值在0.03左右比较正常。而1.444这个值显然偏差非常大。
【结论】
在这个点附近,磁盘压力变得比较大,所以磁盘性能变差)。导致日志文件增长300M无法及时完成。因此该线程拥有的Latch锁无法释放,而其他进程则等待该Latch导致超时。对于这个问题,有以下几种解决方案:
- 更换更好的磁盘。即使在有压力的情况下,性能也不会变得特别差。
- 修改日志文件增长幅度,以每次增长50M为幅度。期望日志文件增长能快速完成。
- 预先对日志文件扩容。这样,就不会有增长日志文件的情况发生。
由于短时间内无法更换磁盘,所以采用第三种方案,预先对日志文件扩容。采用这方案后,再也没有发生Latch timeout。
当然,对于每个Latch timeout的现象,原因可能有所不同,不过通常都是以磁盘性能或操作系统性能造成的原因居多。我们有时候不需要对内存转储进行分析,而是看一下操作系统的日志和数据库的错误日志,也可能找到原因。
【参考资料】
- Diagnosing and Resolving Latch Contention on SQL Server: http://sqlcat.com/sqlcat/b/whitepapers/archive/2011/07/05/diagnosing-and-resolving-latch-contention-on-sql-server.aspx
- Q&A on Latches in the SQL Server Engine http://blogs.msdn.com/b/psssql/archive/2009/07/08/q-a-on-latches-in-the-sql-server-engine.aspx
- Debugging that latch timeout http://troubleshootingsql.com/2011/08/26/debugging-that-latch-timeout/
Buffer Latch Timeout的解析的更多相关文章
- Debugging that latch timeout
https://troubleshootingsql.com/tag/stack-dump/ Book on Azure and SQL Server
- SQL Server 中PAGELATCH_x和PAGEIOLATCH_x解析
0.参考文献 Microsoft SQL Server企业级平台管理实践 第11章 Buffer Latch Timeout的解析 什么是PAGELATCH和PAGEIOLATCH 1.PAGELAT ...
- Latch1:理解 PageIOLatch和PageLatch
Latch主要分为三种,Buffer Latch,I/O Latch, non-buf latch. 1,PageLatch 在访问数据库的数据页(Data Page或Index Page)时,如果相 ...
- PAGELATCH_x 等待--转载
转自出处:http://www.cnblogs.com/xwdreamer/archive/2012/08/30/2663232.html 0.参考文献 Microsoft SQL Server企业级 ...
- SQL Server PageIOLatch和PageLatch
Latch是轻量级的锁,它是SQL Server内部用来同步资源访问的一个数据结构,使数据的访问同步有序,这意味着,当一个线程获得资源R的Latch的独占使用权时,如果其他的线程也想访问这个Latch ...
- PageIOLatch和PageLatch
Latch是轻量级的锁,它是SQL Server内部用来同步资源访问的一个数据结构,使数据的访问同步有序,这意味着,当一个线程获得资源R的Latch的独占使用权时,如果其他的线程也想访问这个Latch ...
- PAGELATCH_x和PAGEIOLATCH_x介绍
Microsoft SQL Server企业级平台管理实践 第11章 Buffer Latch Timeout的解析 什么是PAGELATCH和PAGEIOLATCH 1.PAGELATCH_x和PA ...
- Timeout occurred while waiting for latch: class 'ACCESS_METHODS_DATASET_PARENT'
前些天某个SQL Server数据库的错误日志爆出如下错误: Timeout occurred while waiting for latch: class 'ACCESS_METHODS_DATAS ...
- Oracle Latch的学习【原创】
Latch详解 - MaxChou 本文以学习为目的,大部分内容来自网络转载. 什么是Latch 串行化 数据库系统本身是一个多用户并发处理系统,在同一个时间点上,可能会有多个用户同时操作数据库.多个 ...
随机推荐
- shell中跳出循环语句break和continue
在使用while或for循环语句过程中,也许碰到某个特殊条件,我们需要跳过当次循环或整个循环,这是就需要借助break和continue. break表示跳出本层循环,break n表示跳出循环的层数 ...
- ajax中参数traditional的作用
在使用ajax向后台传值的时候,有的时 $.ajax({ type: "post", async: true, data: { "records": [&quo ...
- consul reconnect_timeout
reconnect_timeout这将控制从集群中彻底删除发生故障的节点需要多长时间.默认值为72小时,建议将其设置为至少为节点或网络分区的预期可恢复的最大停机时间的两倍.警告:将此时间设置得太低可能 ...
- 总结IDEA和VS常用实用的快捷键
整理了我一般撸码时使用下面两个IDE最常用到的快捷键: IntelliJ IDEA: 快捷搜索:Ctrl+F (Match Case:区分大小写,Words:全字匹配,Regex:正则匹配) 批量替换 ...
- win10无法安装软件解决
https://www.windowscentral.com/how-fix-network-resource-unavailable-install-error-windows-10
- c++基础(四)—— 泛型算法
1.find(first, last, value) 头文件:algorithm 参数:前两个参数是“表示元素范围的迭代器”,第三个是一个值 说明:find 将范围中进行寻找.搜索失败:如果范围中无匹 ...
- Linux下使用ip netns命令进行网口的隔离和配置ip地址
1. 添加隔离标记符: ip netns add fd 2. 将指定网卡放入隔离中: ip link set eth1 netns fd 3. 在隔离环境下执行命令: ip netns exec fd ...
- 【LEETCODE】41、905. Sort Array By Parity
package y2019.Algorithm.array; /** * @ProjectName: cutter-point * @Package: y2019.Algorithm.array * ...
- Java doc注释
常用Java注释标签(Java comment tags) @author 作者 适用范围:文件.类.方法 (多个作者使用多个@author标签标识,java doc中显示按输入时间顺序罗列.) 例: ...
- python3+requests:post请求四种传送正文方式
https://www.cnblogs.com/insane-Mr-Li/p/9145152.html 前言:post请求我在python接口自动化2-发送post请求详解(二)已经讲过一部分了,主要 ...