當兩個或多個會話各自持有鎖並請求對方持有的鎖時,形成循環等待鏈,SQL Server 死鎖監控器會選擇「犧牲成本最低」的受害者(通常是撤銷代價最小的事務)強制回滾,釋放鎖資源讓其他會話繼續。錯誤碼 1205 將返回給客戶端。
READ COMMITTED SNAPSHOT (RCSI) 或 SNAPSHOT,用版本存儲替換鎖競爭。啟用 Deadlock Graph 事件(XEvents 推薦 xml_deadlock_report),捕獲完整的受害者/幸存者執行計劃、持鎖資源、輸入緩衝區。定期匯總分析 Top N 死鎖模式,針對性優化。
// C# 範例:指數退避 + 抖動
var delay = TimeSpan.FromMilliseconds(50);
for (int i=0; i<5; i++) {
try { ExecuteTx(); break; }
catch (SqlException ex) when (ex.Number == 1205) {
Task.Delay(delay + TimeSpan.FromMilliseconds(Random.Shared.Next(0, 100))).Wait();
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2);
}
}
隨機抖動是關鍵,避免多個客戶端同步重試再次碰撞。
在存儲過程內部包裹 BEGIN TRAN / COMMIT / ROLLBACK,減少客戶端往返延遲,確保事務要么全做要么全不做,避免「孤兒事務」長期持鎖。
對於「先讀後寫、衝突率低」場景(如庫存扣減、狀態流轉),引入 ROWVERSION / TIMESTAMP 列實現樂觀並發:
UPDATE dbo.Products SET Qty = Qty - @Delta, Ver = Ver + 1
WHERE ProductID = @Id AND Ver = @ExpectedVer;
IF @@ROWCOUNT = 0 THROW 50001, 'Concurrency conflict', 1;
READ COMMITTED SNAPSHOT (RCSI):數據庫級開啟,讀不阻寫、寫不阻讀,極大緩解讀寫衝突。SNAPSHOT:事務級選擇,提供語句級一致性,需應用顯式指定。READ_ONLY、FORWARD_ONLY、LOCAL 三關鍵字缺一不可;更新場景用 OPTIMISTIC 而非 SCROLL_LOCKS。關鍵性能計數器:
SQLServer:Locks(_Total)\Average Wait Time (ms) > 500ms 需告警。SQLServer:Locks(_Total)\Lock Waits/sec 突增說明熱點競爭。sys.dm_os_waiting_tasks、sys.dm_tran_locks 實時定位阻塞鏈。SQL Server 不支持真正的嵌套事務,內層 COMMIT 只遞減 @@TRANCOUNT,只有最外層 COMMIT 真正提交。內層 ROLLBACK 會回滾整個鏈路。建議:
SAVE TRANSACTION 實現部分回滾。死鎖不可完全消除,但通過系統化的「監控→分析→優化→驗證」閉環,可將其頻率壓制到業務可接受水平,保障系統高併發下的穩定運行。