您好,登錄后才能下訂單哦!
SQL Serer閂鎖 和 閂鎖超時故障排除
翻譯自:https://mssqlwiki.com/2012/09/07/latch-timeout-and-sql-server-latch/
在一個多線程的進程里,當一個線程在內存里更新一個數據或索引頁,而另一個線程正在讀取相同的頁,將會發生什么?
當第一個線程在內存里讀取一個數據或索引頁,而第二個線程正在從內存里釋放相同的頁,將會發生什么?
答案是:我們將獲得數據或數據結構不一致的結果。為了避免不一致,SQL Server使用同步機制像鎖(Locks)、閂鎖(Latches)和自旋鎖(Spinlocks)。
在這篇博文里,我們將討論關于閂鎖的一些關鍵點和如何排除閂鎖超時dump故障。
什么是閂鎖(Latch)?
它們通過多線程控制對數據頁和結構的并發訪問。閂鎖提供數據頁的物理數據一致性,并提供數據結果的同步。閂鎖不可以像鎖一樣被用戶控制。
閂鎖的類型:
Buffer(BUF) Latch
用于同步訪問BUF結構和它們相關的數據庫頁。
Buffer "IO" Latch
Buffer Latch的一個子集,用于當BUF和相關的數據/索引頁正在一個IO操作(從磁盤讀取頁或者寫入頁到磁盤)中間時。
Non-Buffer(Non-BUF) Latch
這些閂鎖被用于同步一般的內存中數據結構,這些結構通常被并行線程、自動增長操作和收縮操作等查詢/任務執行所使用。
閂鎖模式:
Keep(KP) Latches
用于確保當頁面正在使用時,不會從內存釋放。
Shared(SH) Latches
用于對數據結構的只讀訪問,和阻止其他線程的寫訪問。
這個模式允許共享訪問。
SH可兼容于KP、SH和UP。應該注意的是,盡管通常SH表明了只讀訪問,但不總是這樣。對于Buffer Latches,SH是為了讀取一個數據頁的最小模式要求。
Update(UP) Latches
允許對數據結構(兼容于SH和KP)的讀訪問,但是阻止其他EX-latch訪問。
當頁分裂檢測關閉并且當AWE沒有啟用時用于寫操作。
Exclusive(EX) Latches
阻止發生在被閂鎖結構上的讀取活動。EX只兼容于KP。
當頁分裂檢測開啟或者AWE啟用時在讀IO和寫IO期間。
Destroy(DT) Latches
用于當從Buffer Pool移除BUFS,要么通過添加它們到空閑列表,要么取消映射AWE Buffers。
閂鎖兼容性:
KP SH UP EX DT
KP Y Y Y Y N
SH Y Y Y N N
UP Y Y N N N
EX Y N N N N
DT N N N N N
如何識別閂鎖爭用?
閂鎖爭用可以通過在sysprocesses里的等待類型來識別。
PAGEIOLATCH_*:
這個sysprocesses里的等待類型表明SQL Server正在等待一個Buffer Pool頁的物理I/O完成。
1.PAGEIOLATCH_*通常通過調優查詢來解決,該查詢會執行大量的IO操作(通常通過添加、修改和移除索引或統計信息來介紹物理IO數量)。
2.識別是否有磁盤瓶頸并修復它們(PAGEIOLATCH等待時間(例如大于30ms))。
PAGELATCH_*:
這個sysprocesses里的等待類型表明SQL Server正在等待訪問一個數據庫頁,但是該頁沒有經歷物理IO。
1.這個問題通常由在同一時間試圖訪問相同物理頁的大量會話導致。你有應該查看spid的等待資源。這個wait_resource是被訪問的頁號(格式是dbid:file:pageno)。
2.我們可以使用DBCC PAGE來識別對象或者發生爭用的頁類型。它也幫助我們確定是否爭用是用于分配、數據或文本。
3.如果SQL Server最頻繁等待的頁面在tempdb數據庫,在dbid為2對于一個頁號檢查等待資源列。你可能面臨著在這里提到的tempdb閂鎖爭用:http://support.microsoft.com/kb/328551
4.如果頁在一個用戶數據庫,檢查是否表在一個單調鍵像標識列上有一個聚集索引,所有的線程正在爭用表末尾的相同頁。在這種情況下,我們需要選擇一個不同的聚集索引鍵,將工作分散到不同的頁。
LATCH_*:
Non-buf閂鎖等待可以被各種事情導致。我們可以在sysprocesses里使用這個等待資源列來確定包含的閂鎖類型(KB 822101)。
1.一個非常普通的LATCH_EX等待是由于運行一個Profiler跟蹤或者sp_trace_getdata參考KB 929728獲取更多信息。
2.自動增長和自動收縮。
當一個閂鎖被線程請求,并且如果由于其他線程在相同的頁或數據結構上持有一個不兼容的閂鎖,而這個閂鎖不能被立即授予,那么這個請求者必需等待閂鎖可被授予。如果等待間隔達到5分鐘(waittime 300),類似以下的一條警告信息在SQL Server錯誤日志中輸出,并且所有線程的一個mini dump被捕獲。警告信息對buffer和non-buffer latches有所區別。
844: Time out occurred while waiting for buffer latch — type %d, bp %p, page %d:%d, stat %#x, database id: %d, allocation unit id: %I64d%ls, task 0x%p : %d, waittime %d, flags 0x%I64x, owning task 0x%p. Continuing to wait. 846: A time-out occurred while waiting for buffer latch — type %d, bp %p, page %d:%d, stat %#x, database id: %d, allocation unit Id: %I64d%ls, task 0x%p : %d, waittime %d, flags 0x%I64x, owning task 0x%p. Not continuing to wait. 847: Timeout occurred while waiting for latch: class ‘%ls’, id %p, type %d, Task 0x%p : %d, waittime %d, flags 0x%I64x, owning task 0x%p. Continuing to wait.
拆分以上警告
類型(type):
當前閂鎖獲取請求的閂鎖模式。這個一個使用如下匹配的numerical值:
0 – NL (not used); 1 – KP; 2 – SH; 3 – UP; 4 – EX; 5 – DT.
任務(task):
我們嘗試得到閂鎖的任務。
等待時間(Waittime):
等待閂鎖獲取請求的以秒為單位的總時間。
擁有的任務(owning task):
可用的擁有閂鎖的任務地址。
bp(只有Buffer latches):
與Buffer latch對應的BUF結構的地址。
page(只有Buffer latches):
對于當前包含在BUF結構中的頁的頁ID。
database id(只有Buffer latches):
對于在BUF里的頁的數據庫ID。
像排除SQL Server里的阻塞問題一樣,當有一個閂鎖爭用或者超時dump,識別閂鎖的所有者并故障排除為什么閂鎖被該所有者長時間持有。
當有閂鎖超時dump,你可以看到類似以下一個的警告信息。在dump是非常重要的用于找到閂鎖的所有者線程之前,警告錯誤信息打印在SQL Server錯誤日志里。
2012-01-18 00:52:03.16 spid69 A time-out occurred while waiting for buffer latch — type 4, bp 00000000ECFDAA00, page 1:6088, stat 0x4c1010f, database id: 4, allocation unit Id: 72057594043367424, task 0x0000000006E096D8 : 0, waittime 300, flags 0x19, owning task 0x0000000006E08328. Not continuing to wait. spid21s **Dump thread – spid = 21, PSS = 0x0000000094622B60, EC = 0x0000000094622B70 spid21s ***Stack Dump being sent to E:\Data\Disk1\MSSQL.1\MSSQL\LOG\SQLDump0009.txt spid21s * ******************************************************************************* spid21s * BEGIN STACK DUMP: spid21s * 02/28/12 00:32:03 spid 21 spid21s * Latch timeout Timeout occurred while waiting for latch: class ‘ACCESS_METHODS_HOBT_COUNT’, id 00000002D8C32E70, type 2, Task 0x00000000008FCBC8 : 7, waittime 300, flags 0x1a, owning task 0x00000000050E1288. Continuing to wait. Timeout occurred while waiting for latch: class ‘ACCESS_METHODS_HOBT_VIRTUAL_ROOT’, id 00000002D8C32E70, type 2, Task 0x00000000008FCBC8 : 7, waittime 300, flags 0x1a, owning task 0x00000000050E1288. Continuing to wait.
從以上錯誤信息,我們可以很容易理解,我們正嘗試在數據庫ID為4,頁1:6088(第一個文件的6088頁)請求閂鎖,并且因為任務0x0000000006E08328(在警告信息中擁有任務0x0000000006E08328)正在它上面持有一個閂鎖而超時。
注意:任務只是被線程執行的一個工作請求。(就像system task、login task和ghost cleanup task等)。執行這個任務的線程將按需持有需要的閂鎖。
讓我們看看如何分析閂鎖超時dump和使用擁有任務0x0000000006E08328獲取閂鎖的擁有線程。
去分析dump,從這里http://sdrv.ms/MO6ytG下載和安裝Windows Debugger。
步驟1:
打開Windbg。選擇“File”菜單,選擇“Open crash dump”,選擇“Dump file”(SQLDump000#.mdmp)。
步驟2:
在命令行窗口輸入
.sympath srv*c:\Websymbols*http://msdl.microsoft.com/download/symbols;
步驟3:
輸入.reload /f并回車。這將強制debugger立即加載所有的符號。
步驟4:
通過使用debugger命令lmvm驗證是否符號被SQL Server加載。
0:002> lmvm sqlservr
start end module name
00000000`01000000 00000000`03679000 sqlservr T (pdb symbols) c:\websymbols\sqlservr.pdb\21E4AC6E96294A529C9D99826B5A7C032\sqlservr.pdb
Loaded symbol p_w_picpath file: sqlservr.exe
Image path: C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe
Image name: sqlservr.exe
Timestamp: Wed Oct 07 21:15:52 2009 (4ACD6778)
CheckSum: 025FEB5E
ImageSize: 02679000
File version: 2005.90.4266.0
Product version: 9.0.4266.0
File flags: 0 (Mask 3F)
File OS: 40000 NT Base
File type: 1.0 App
File date: 00000000.00000000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
步驟5:
使用以下命令來搜索線程堆棧來識別與擁有的任務相關的線程,并且它是擁有閂鎖的線程。在你的錯誤日志里使用擁有的任務替代0X0000000006E08328
~*e .echo ThreadId:; ?? @$tid; r? @$t1 = ((ntdll!_NT_TIB *)@$teb)->StackLimit; r? @$t2 = ((ntdll!_NT_TIB *)@$teb)->StackBase; s -d @$t1 @$t2 0X0000000006E08328
ThreadId:
unsigned int 0x93c
ThreadId:
unsigned int 0x9a0
ThreadId:
unsigned int 0x9b4
00000000`091fdaf0 06e08328 00000000 00000000 00000000 (……………
00000000`091fdcb8 06e08328 00000000 091fdd70 00000000 (…….p…….
00000000`091fded0 06e08328 00000000 06e0e798 00000000 (……………
00000000`091fdf38 06e08328 00000000 00000002 00000000 (……………
00000000`091fec60 06e08328 00000000 0168883a 00000000 (…….:.h…..
00000000`091ff260 06e08328 00000000 000007d0 00000000 (……………
00000000`091ff2d0 06e08328 00000000 00000020 00000000 (……. …….
00000000`091ff5f8 06e08328 00000000 800306c0 00000000 (……………
00000000`091ff6c0 06e08328 00000000 00000000 00000000 (……………
00000000`091ff930 06e08328 00000000 00000000 00000001 (……………
00000000`091ff9b8 06e08328 00000000 00000000 00000000 (……………
00000000`091ffa38 06e08328 00000000 00000000 00000000 (……………
00000000`091ffc10 06e08328 00000000 03684080 00000000 (……..@h…..
00000000`091ffc90 06e08328 00000000 00000000 00000000 (……………
ThreadId:
unsigned int 0x9b8
ThreadId:
unsigned int 0x9bc
ThreadId:
unsigned int 0x9c0
……………
…………..
步驟6:
從以上輸出,我們可以看到線程0x9b4與擁有的任務的指針相關,并且它是擁有閂鎖的線程。讓我們切換到線程(0x9b4),它正在執行擁有的任務,然后瀏覽這個堆棧來查看為什么這個線程長時間持有閂鎖。
步驟7:
~~[0x9b4]s ==> Switching to the thread (Replace 0x9b4 with your thread id which has reference to the po
ntdll!ZwWaitForSingleObject+0xa:
00000000`77ef047a c3 ret
步驟8:
0:002> kC ==> Print the stack
Call Site
ntdll!ZwWaitForSingleObject
kernel32!WaitForSingleObjectEx
sqlservr!SOS_Scheduler::SwitchContext
sqlservr!SOS_Scheduler::Suspend
sqlservr!SOS_Event::Wait
sqlservr!BPool::FlushCache
sqlservr!checkpoint2
sqlservr!alloca_probe
sqlservr!ProcessCheckpointRequest
sqlservr!CheckpointLoop
sqlservr!ckptproc
sqlservr!SOS_Task::Param::Execute
sqlservr!SOS_Scheduler::RunTask
sqlservr!SOS_Scheduler::ProcessTasks
sqlservr!SchedulerManager::WorkerEntryPoint
sqlservr!SystemThread::RunWorker
sqlservr!SystemThreadDispatcher::ProcessWorker
sqlservr!SchedulerManager::ThreadEntryPoint
msvcr80!endthreadex
msvcr80!endthreadex
從以上堆棧,我們可以理解,擁有閂鎖的線程正執行檢查點并刷新緩存(臟頁)到磁盤。如果刷新緩存到磁盤(檢查點)花費很長時間,那么顯然是有磁盤瓶頸。
類似的,對于其他閂鎖超時問題,首先識別閂鎖的擁有者線程,讀取擁有者線程的堆棧,來理解擁有者線程執行的任務,并排除由擁有者線程執行的任務引起的性能故障。
如果你想查看等待的線程的堆棧,那么在錯誤日志里從閂鎖超時警告信息獲取任務(任務0x0000000006E096D8)代替擁有者任務(任務0x0000000006E08328),并使用在步驟5中提到的命令。
我希望這篇博文將幫助你學習和排除閂鎖超時故障。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。