天天看點

SQL Server裡的自旋鎖介紹

在上一篇文章裡我讨論了SQL Server裡的闩鎖。在文章的最後我給你簡單介紹了下自旋鎖(Spinlock)。基于那個基礎,今天我會繼續讨論SQL Server中的自旋鎖,還有給你展示下如何對它們進行故障排除。

為什麼我們需要自旋鎖?

在上篇文章我已經指出,用闩鎖同步多個線程間資料結構通路,在每個共享資料結構前都放置一個闩鎖沒有意義的。闩鎖與此緊密關聯:當你不能獲得闩鎖(因為其他人已經有一個不相容的闩鎖拿到),查詢就會強制等待,并進入挂起(SUSPENDED)狀态。查詢在挂起狀态等待直到可以拿到闩鎖,然後就會進入可執行(RUNNABLE)狀态。對于查詢執行隻要沒有可用的CPU,查詢就一直在可執行(RUNNABLE)狀态。一旦CPU有空閑,查詢會進入運作(RUNNING)狀态,最後成功擷取到闩鎖,用它來保護通路的共享資料結構。下圖展示了SQLOS對協調線程排程實作的狀态機。 

SQL Server裡的自旋鎖介紹

.

因為太多關聯的闩鎖,對“忙碌”資料結構使用闩鎖保護沒有意義。是以SQL Server實作所謂自旋鎖(Spinlocks)。自旋鎖就像一個闩鎖,存儲引擎使用的一個輕量級同步對象,用來同步對共享資料結構線程通路。和闩鎖的主要差別是你積極等待自旋鎖——不離開CPU。在自旋鎖上的“等待”總會發生在運作(RUNNING)狀态的CPU。在你閉合循環裡旋轉直到獲得自旋鎖。這就是所謂的忙碌等待(busy wait)。自旋鎖的最大優點是當查詢在自旋鎖上等待時,不會涉及到上下文切換。另一方面忙碌等待浪費CPU周期,其他查詢也許能對它們更有效的使用。

為了避免太多的CPU周期浪費,SQL Server 2008 R2及後續版本實作所謂的指數補償機制(exponential backoff mechanism),那裡在CPU上一些時間的休眠後,線程停止旋轉。線上程進入休眠期間,增加了嘗試獲得自旋鎖的逾時。這個行為可以降低對CPU性能的影響。

(補充說明:Spinlock中文可以稱為自旋鎖。它是一個輕量級的,使用者态的同步對象,和critical section類似,但是粒度比前者小多了。它主要用來保護某些特定的記憶體對象的多線程并發通路。Spinlock是排他性的。一次隻能一個線程擁有。

Spinlock的設計目标是非常快和高效率。Spinlock内部如何工作呢?它首先試圖獲得某個對象的鎖,如果目标被其它線程占有,就在那裡輪詢(spin)一定時間。如果還得不到鎖,就sleep一小會,然後繼續spin。反複這個過程直到得到對象的占有權。) 

自旋鎖與故障排除

對自旋鎖故障排除的主要DMV是 sys.dm_os_spinlock_stats。這個DMV裡傳回的每一行都代表SQL Server裡的一個自旋鎖。SQL Server 2014實作了262個不同自旋鎖。我們來詳細看下這個DMV裡的各個列:

  • name:自旋鎖名稱
  • collisions:當嘗試通路保護的資料結構時,被自旋鎖阻塞的線程次數
  • spins:在循環裡嘗試獲得自旋鎖的自旋鎖線程次數
  • spins_per_collision:旋轉和碰撞之間的比率
  • sleep_time:因為退避線程休眠時間
  • backoffs::為了其他線程在CPU上繼續,線程退避次數

在這個DMV裡最重要的列是backoffs,對于特定的自旋鎖類型,這列告訴你退避發生頻率。高頻率的退避會屈服于CPU消耗引起SQL Server裡的自旋鎖競争(Spinlock Contention)。我就見過一個32核的SQL Server伺服器,CPU運作在100%而不進行任何工作——典型的自旋鎖競争症狀。 

對自旋鎖問題進行故障排除你可以使用擴充事件提供的sqlos.spinlock_backoff。當退避(backoff)發生時,就會觸發這個擴充事件。如果你捕獲了這個事件,你還要保證你使用非常好的選擇性謂語,因為在SQL Server裡退避會經常發生。一個好的謂語可以是特定的自旋鎖類型,通過剛才提到的DMV你已經看到。下列代碼給你展示了如何建立這樣的擴充事件會話。

1 -- Retrieve the type value for the LOCK_HASH spinlock.
 2 -- That value is used by the next XEvent session
 3 SELECT * FROM sys.dm_xe_map_values
 4 WHERE name = 'spinlock_types'
 5 AND map_value = 'LOCK_HASH'
 6 GO
 7 
 8 -- Tracks the spinlock_backoff event
 9 CREATE EVENT SESSION SpinlockContention ON SERVER 
10 ADD EVENT sqlos.spinlock_backoff
11 (
12     ACTION
13     (
14         package0.callstack
15     )
16     WHERE
17     (
18         [type] = 129 -- <<< Value from the previous query
19     )
20 ) 
21 ADD TARGET package0.histogram
22 (
23     SET source = 'package0.callstack', source_type = 1
24 )
25 GO      

從代碼裡可以看到,這裡我在調用堆棧(callstack)上使用了直方圖(histogram)目标來bucktize。是以對于特定的自旋鎖,你可以可能到SQL Serve裡生成的最高退避(backoffs)代碼路徑。你甚至可以通過啟用3656跟蹤标記(trace flag)來辨別調用堆棧。這裡你可以看到來自這個擴充會話的輸出:

sqldk.dll!XeSosPkg::spinlock_backoff::Publish+0x138

sqldk.dll!SpinlockBase::Sleep+0xc5

sqlmin.dll!Spinlock<129,7,1>::SpinToAcquireWithExponentialBackoff+0x169

sqlmin.dll!lck_lockInternal+0x841

sqlmin.dll!XactWorkspaceImp::GetSharedDBLockFromLockManager+0x18d

sqlmin.dll!XactWorkspaceImp::GetDBLockLocal+0x15b

sqlmin.dll!XactWorkspaceImp::GetDBLock+0x5a

sqlmin.dll!lockdb+0x4a sqlmin.dll!DBMgr::OpenDB+0x1ec

sqlmin.dll!sqlusedb+0xeb

sqllang.dll!usedb+0xb3

sqllang.dll!LoginUseDbHelper::UseByMDDatabaseId+0x93

sqllang.dll!LoginUseDbHelper::FDetermineSessionDb+0x3e1

sqllang.dll!FRedoLoginImpl+0xa1b

sqllang.dll!FRedoLogin+0x1c1

sqllang.dll!process_request+0x3ec

sqllang.dll!process_commands+0x4a3

sqldk.dll!SOS_Task::Param::Execute+0x21e

sqldk.dll!SOS_Scheduler::RunTask+0xa8

sqldk.dll!SOS_Scheduler::ProcessTasks+0x279

sqldk.dll!SchedulerManager::WorkerEntryPoint+0x24c

sqldk.dll!SystemThread::RunWorker+0x8f

sqldk.dll!SystemThreadDispatcher::ProcessWorker+0x3ab

sqldk.dll!SchedulerManager::ThreadEntryPoint+0x226

使用提供調用堆棧,不難找出自旋鎖競争發生的地方。在那個指定的笤俑堆棧裡競争發生在LOCK_HASH自旋鎖類型裡,它是保護鎖管理器的哈希表。每次在鎖管理器裡加鎖或解鎖被執行時,自旋鎖必須在對應的哈希桶裡獲得。如你所見,在調用堆棧裡,當從XactWorkspacelmp類調用GetSharedDBLockFromLockManager函數時,自旋鎖被獲得。這表示當競争到資料庫時,共享資料庫鎖被嘗試擷取。最後在用很高的退避(backoffs)的LOCK_HASH自旋鎖裡,這屈服于自旋鎖競争。

小結

這篇文章裡你學習了SQL Server裡的自旋鎖。在第1部分我們讨論了為什麼SQL Server需要實作自旋鎖。如你所見,使用自旋鎖保護自并發線程對“忙碌”共享資料結構的通路更“便宜”——例如鎖管理器。在第2部分我們詳細讨論了對SQL Server的自旋鎖競争你如何進行故障排除,還有使用辨別的調用堆棧如何找出問題的根源。

感謝關注!

參考文章:

https://www.sqlpassion.at/archive/2014/06/30/introduction-to-spinlocks-in-sql-server-2/

注:此文章為

WoodyTu

學習MS SQL技術,收集整理相關文檔撰寫,歡迎轉載,請在文章頁面明顯位置給出此文連結!

若您覺得這篇文章還不錯請點選下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!