SQL Server裡的閂鎖介紹
在今天的文章裡我想談下SQL Server使用的更高階的,輕量級的同步物件:閂鎖(Latch)。閂鎖是SQL Server儲存引擎使用輕量級同步物件,用來保護多執行緒訪問記憶體內結構。文章的第1部分我會介紹SQL Server裡為什麼需要閂鎖,在第2部分我會給你介紹各個閂鎖型別,還有你如何能對它們進行故障排除。
為什麼我們需要閂鎖?
閂鎖首次在SQL Server 7.0裡引入,同時微軟首次引入了行級別鎖(row-level locking)。對於行級別鎖引入閂鎖的概念是非常重要的,不然的話在記憶體中會出現丟失更新(Lost Updates)的現象。如我所說的,閂鎖是儲存引擎使用的輕量級同步物件,是SQL Server用來保護記憶體結構的。閂鎖只不過是類似於多執行緒程式設計裡的所謂的臨界區(Critcal Section)概念。
在傳統併發程式設計裡,臨界區是同時只能一個執行緒執行的程式碼。閂鎖本身是個臨界區的特殊版本,因為它允許多個併發讀操作。在SQL Server的上下文裡這意味著多個執行緒可以併發讀取一個共享資料結構,例如記憶體中的頁,但是寫入共享資料結構必須是單個執行緒進行。
閂鎖是用來協調資料庫裡多個執行緒物理執行,然而鎖是基於選擇的事務隔離級別,用來邏輯獲得需要的隔離級別。作為開發者或DBA的你,你可以用不同方式影響鎖——例如通過SQL Server裡的隔離級別,或者通過各種可用鎖提示。另一方面閂鎖是不能以直接方式控制的。在SQL Server裡沒有閂鎖提示,也沒有可用閂鎖隔離級別。下表是鎖和閂鎖之間的比較:
鎖(Locks) 閂鎖(Latches)
- 控制…… 事務 執行緒
- 保護…… 資料庫內容 記憶體中資料結構
- 模式…… 共享的(Shared), 保持(Keep),
更新(Update), 共享的(Shared),
排它的(Exclusive), 更新(Update),排它的(Exclusive),
意向的(Intension) 銷燬(Destroy)
- 死鎖…… 檢測並解決(detection&resolution) 通過嚴謹程式碼來避免
- 保持在…… 鎖管理器的雜湊表(Hashtable) 保護的資料結構(Protected Data Structure)
從表裡可以看到,閂鎖支援更細粒度保持(Keep)和銷燬(Destroy)模式。保持閂鎖主要用來引用計數,例如當你想知道在指定閂鎖上有多少其它閂鎖在等待。銷燬閂鎖是最有限制的一個(它甚至會阻塞保持閂鎖),當閂鎖被銷燬時會用到,例如當惰性寫入器(Lazy Writer)想要釋放記憶體中的頁時。我們先介紹下各種閂鎖模式,然後說下各個閂鎖模式的相容性。
NL(空閂鎖):
- 內部
- 未使用
KP(保持閂鎖):
- 可以由多個任務同時持有
- 只被一個DT模式的閂鎖阻塞
SH(共享閂鎖):
- 讀取資料頁的時候使用
- 可以由多個任務同事持有
- 阻塞EX模式和DT模式的閂鎖
UP(更新閂鎖):
- 寫入系統分配頁面和tempdb的行版本化頁面時使用
- 一個這種模式的閂鎖只能被一個單獨的任務持有
EX(排它閂鎖):
- 寫入資料頁的時候使用
- 一個這種模式的閂鎖只能被一個單獨的任務持有
DT(銷燬閂鎖):
- 很少使用
- 一個這種模式的閂鎖只能被一個單獨的任務持有
在SQL Server裡,一致性不能只用鎖來獲得。SQL Server還是可以訪問沒被鎖管理器保護的共享資料結構,例如頁頭。還有SQL Server基於閂鎖基礎的其他元件也是,有單執行緒程式碼路徑。好了,我們繼續講解SQL Server裡的各種閂鎖型別,還有如何對它們進行故障排除。
閂鎖型別與故障排除
SQL Server區分3個不同閂鎖類別
- IO閂鎖
- 緩衝區閂鎖(BUF)
- 非緩衝區閂鎖(Non-BUF)
我們來詳細看下這3個不同類別。當在緩衝池的頁讀寫操作未完成——即當你讀自/寫入你的儲存子系統時(2者未同步),SQL Server會使用IO閂鎖(I/O Latches)。對於這些I/O閂鎖,SQL Server在統計資訊裡以PAGEIOLATCH_為前綴出現。你可以在DMV sys.dm_os_wait_stats 檢視下這些閂鎖型別的等待。
1 SELECT * FROM sys.dm_os_wait_stats WHERE wait_type LIKE 'PAGEIOLATCH_%'
使用這些閂鎖,SQL Server保證那些頁不會併發多次讀入快取池,那些頁也不會從快取池忽略,在那些頁需要被查詢訪問的時候。
除這些I/O閂鎖外,SQL Server也支援所謂的快取區閂鎖(Buffer Latches),它用來保護緩衝池裡頁不會被併發執行的執行緒影響。這些閂鎖,SQL Server使用它們來阻止記憶體中的丟失更新(Lost Updates)。沒有這類閂鎖,在快取池裡會有併發的讀寫頁,它們會引發主記憶體裡頁的損壞。對於這些快取閂鎖,SQL Server在統計資訊裡以PAGELATCH_為前綴出現。你可以在DMV sys.dm_os_wait_stats 檢視下這些閂鎖型別的等待。這裡最重要的是你涉及了主記憶體的競爭,因為他們的等待型別名稱裡不包含IO字樣。
1 SELECT * FROM sys.dm_os_wait_stats WHERE wait_type LIKE 'PAGELATCH_%'
最後SQL Server內部使用所謂的非快取區閂鎖(Non-Buffer Latches)來保護除緩衝池外的共享資料結構。對於這些非快取閂鎖,SQL Server在統計資訊裡以LATCH_為前綴出現。你可以在DMV sys.dm_os_wait_stats 檢視下這些閂鎖型別的等待。
1 SELECT * FROM sys.dm_os_wait_stats WHERE wait_type LIKE 'LATCH_%'
但在這個DMV裡這些對於非快取區閂鎖的等待只是SQL Server內部使用的各個閂鎖的概況資訊,你可以在單獨的DMV sys.dm_os_latch_stats找到更詳細的資訊。
1 SELECT * FROM sys.dm_os_latch_stats
SQL Server 2014內部使用163個閂鎖來同步共享資料結構的訪問。其中一個著名的閂鎖是FGCB_ADD_REMOVE,它用來保護檔案組的檔案組控制阻塞( File Group Control Block (FGCB)),在以下特定操作期間:
- 檔案增長(手動或自動)
- 增加/刪除檔案組檔案
- 重新計算填充比重(Recalculating proportional fill weightings)
- 在迴圈分配期間,通過檔案組的檔案回收。
當你看到這個閂鎖排在前列是,你肯定有太多自動增長操作的問題,原因是你資料庫糟糕的預設配置。當查詢嘗試讀/防寫的資料結構且需要等待一個閂鎖時,查詢就會進入掛起狀態,直到閂鎖可以成功獲取。因此查詢經過的整個查詢生命週期包括執行(RUNNING),掛起(SUSPENDED),可執行(RUNNABLE),最後再次執行(RUNNING)。因此,當查詢長時間把持閂鎖時,強制共享資料結構保護才有意義。那是因為改變查詢狀態也意味著進行Windows系統裡的上下文切換,依據引入的CPU週期是個很昂貴的操作。
因此對於讀寫頻繁或極短時間內的共享資料結構上放上閂鎖沒有意義。在這個情況下,需要的上下文切換會殺死SQL Server的整體效能,它需要花費太多的時間來完成整個查詢生命週期(執行(RUNNING),掛起(SUSPENDED),可執行(RUNNABLE))。那就是是SQL Server引入的所謂自旋鎖(Spinlocks)。鎖管理器就是這樣資料結構的好例子:當鎖定或解鎖資料物件(例如記錄,頁等)時只需要單個執行緒訪問。但當你檢視sys.dm_os_latch_stats時,你會發現沒有閂鎖保護鎖管理器本身。鎖管理器使用的雜湊表裡對應的雜湊桶使用自旋鎖來保護——LOCK_HASH自旋鎖。通過鎖管理器執行鎖定和解鎖操作前,必須獲得自旋鎖。但今天我不想再講解自旋鎖了,因為我會在專門的文章裡詳細介紹——耐心期待下吧:)
小結
在這個文章裡,我們探討了SQL Server裡的閂鎖。如你所見,閂鎖是SQL Server使用的輕量級同步物件,用來儲存記憶體裡的共享資料結構。SQL Server區分3個不同閂鎖型別——IO閂鎖,快取區閂鎖和非快取區閂鎖。你也看到了如何使用DMV sys.dm_os_wait_stats 和sys.dm_os_latch_stats進行閂鎖等待的故障排除。
感謝關注!