阻塞與死鎖 (Blocking & Deadlocks)

★★★★Draft

FromSQL Server 2025 Query Performance Tuning

📚 From the Books

阻塞與死鎖 (Blocking & Deadlocks)Query Processing

Locking 每次讀寫都在發生、正常;當一個交易持鎖、另一個要相同資源只能等,就是 blocking;兩邊各持對方要的鎖、誰都動不了,就升級成 deadlock。這頁不談鎖的理論,只談 production 卡住或死鎖時,怎麼查、怎麼解。

🧠 Intuition

TIP

三個詞要分清楚:Locking 是協調存取的機制(一直發生)、Blocking 是「B 在等 A 放鎖」的等待、Deadlock 是等待成了環(必有一方被犧牲)。查 blocking 先找 head blocker(那條沒在等別人、卻擋住一串的交易);查 deadlock 先拿到 deadlock graph。

⚖️ Tradeoffs

觀測 Blocking — 先找 head blocker
  • sys.dm_exec_requestsblocking_session_id 直接告訴你「誰被誰擋」;順著鏈往上找到那條 blocking_session_id = 0、自己沒在等的,就是 head blocker。
  • sys.dm_tran_locks 看單一 session 實際持有/等待哪些鎖:resource_type(RID/KEY/PAGE/OBJECT)、request_mode(S/X/U/IS/IX/SIX)、request_statusGRANT 已取得 / WAIT 被擋)。
  • SSMS 內建 Activity Monitor 能看 lock waits 與 head blocker,是臨場快速定位的工具。
  • 長期觀測靠 Extended Events 的 blocked_process_report:把 blocked process threshold 設 5–10 秒,超過門檻就吐 XML 報告供事後分析——production 最低成本的做法。
Blocking 的常見成因 — 鎖範圍太大或持太久
  • 缺索引:查詢被迫全表掃描,鎖範圍暴增;設計索引讓鎖精準落在 KEY/RID 而非 PAGE/TAB。
  • 交易太長:把外部 API 呼叫、使用者輸入包進 transaction,鎖就一直握著;持鎖越久、後面等的人越多。
  • isolation 過高:預設的 Read Committed 讀完即放 S 鎖,但更高的級別(Repeatable Read/Serializable)會把鎖持有到 commit,等待鏈更長。
  • cursor / 逐列處理:逐列鎖定又慢,是 blocking 與 deadlock 的溫床,能改 set-based 就改。
Lock Escalation — 省記憶體卻擋住整表
  • 太多細粒度鎖(每列一把 RID/KEY)會吃爆記憶體,SQL Server 會把幾千把細鎖換成一把表級鎖。
  • 觸發門檻大約是單一語句鎖到 5,000 列,升級目標是 TABLE(或 partition table 的 HoBT)。
  • 副作用:對 OLTP 是災難——一張表突然被獨佔,所有讀寫全卡住。
  • 緩解:批次操作分塊、每批 < 5,000 列;partition table 設 LOCK_ESCALATION = AUTO;用索引讓鎖落在 KEY。
Deadlock 的三種典型成因
  • 循環依賴 (cycle):A 拿 R1 想要 R2、B 拿 R2 想要 R1——存取順序不一致造成的環。
  • 鎖升級衝突 (conversion):A、B 各持同一資源的 S 鎖,同時想升級成 X,互相卡死。
  • trigger 偷加的存取:input 看似只動一張表,但 trigger 暗中又 UPDATE 了另一張,湊成循環——deadlock 報告的 <executionStack> 若含 trigger 名稱,它通常是真兇。
  • 內部的 lock monitor 持續掃描、發現循環就選一個估算成本較低的當 victim 回滾,讓另一個繼續(標準錯誤是 Msg 1205)。
拿到並解讀 Deadlock Graph
  • 三件必須拿到的資訊:Sessions(誰跟誰打架)、Resources(爭哪些鎖)、Queries(實際的 SQL)。
  • 免費第一站:SQL Server 自 2008 起預設開啟的 system_health Extended Events session 自動記錄 deadlock graph,什麼都不用設;但它用固定大小檔案輪替、舊資料會被覆寫,要長期保存得自建 XE。
  • 自訂 XE 用 xml_deadlock_report(含查詢文字,最好找根因)或 xml_deadlock_report_filtered(去掉查詢文字以符合隱私規範);另有 Trace flag 1222 落地到 error log。
  • 用 SSMS 開 graph:被打 X 的 process 是 victim;XML 的 <inputbuf> 是使用者送出的查詢、<executionStack> 顯示真正觸發鎖的內部呼叫(如 trigger)。
化解與預防 — 重試是補丁,根因要修
  • 統一存取順序:讓所有交易都以固定順序(先表 A、再表 B)操作,就永遠不會形成循環——這是 deadlock 的根本解。
  • 縮短交易 + 好索引:把昂貴查詢、外部呼叫移出 transaction;用索引縮小鎖範圍、降低碰撞機率。
  • 降 isolation / 用快照:多數 OLTP 開 RCSI 是最佳折衷(讀者不擋寫者、寫者不擋讀者,仍維持 read committed 語意),能消除大量 read-vs-write 衝突;代價是 tempdb 要存 row version。不要用 WITH (NOLOCK) 換快,那是把問題變成 dirty data。
  • 重試 victim:deadlock 是可預期的執行時錯誤,程式應捕捉 ERROR_NUMBER() = 1205、小退避後有限次重試——但重試是補丁,不能取代修根因。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading