萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL Redo Log 死鎖問題深度排查:從堆棧分析到 Memory Barrier 根因定位

問題背景

多實例壓測並發 384 時,新版本 MySQL Server hang 住:無法執行任何事務語句、所有線程等待、無法 KILL。原生版本無此問題,現場環境保留完好,為根因分析提供寶貴現場。

現場證據收集

  • pstack 獲取進程堆棧快照
  • pt-pmp 聚合分析堆棧,統計線程分布

關鍵觀察:大量線程等待 log_sys->mutex,少數後台線程在寫 Redo Log、操作 Flush List、Purge、Change Buffer。

問題分析三部曲

問題一:誰持有 log_sys->mutex?

篩選條件:單線程、堆棧含 mtr_log_reserve_and_write 或 log_write_up_to。鎖定 Thread 446:已獲取 log_sys->mutex,正在嘗試獲取 log_sys->log_flush_order_mutex 以將髒頁加入 Flush List,但阻塞於此。

問題二:log_flush_order_mutex 被誰佔用?

  • 代碼檢索:僅在 log_flush_order_mutex_enter/exit 兩處操作
  • 堆棧掃描:無其他線程持有該鎖
  • GDB 附加檢查:log_flush_order_mutex = {waiters=1, lock_word=0} —— 鎖處於空閒狀態,但有 1 個等待者(Thread 446)

結論:釋放鎖的線程未喚醒等待者,導致 Thread 446 永久等待,進而阻塞所有需 log_sys->mutex 的線程,實例整體 hang 死。

問題三:為何喚醒機制失效?

深入 mutex_exit_func 代碼註釋發現關鍵線索:

「因 memory barrier 存在,mutex_get_waiters 與 mutex_reset_lock_word 執行順序可能顛倒,導致 hang。專門提供 sync_arr_wake_threads_if_sema_free() 解決此問題。」

  • sync_arr_wake_threads_if_sema_free() 僅在 srv_error_monitor_thread 中調用
  • 該線程專職監控內部異常、打印錯誤、執行 600 秒自殺邏輯
  • 堆檢查發現:srv_error_monitor_thread 正嘗試獲取 log_sys->mutex 而阻塞!

死鎖鏈完整還原

  1. Thread 446 獲取 log_sys->mutex,嘗試獲 log_flush_order_mutex 入 Flush List
  2. Thread X 釋放 log_flush_order_mutex 時,因 Memory Barrier 導致喚醒指令重排,未喚醒 Thread 446
  3. Thread 446 永久等待 log_flush_order_mutex,持續持有 log_sys->mutex
  4. srv_error_monitor_thread 需獲取 log_sys->mutex 執行喚醒檢查,被 Thread 446 阻塞
  5. 喚醒檢查無法執行 → Thread 446 永不被喚醒 → 所有需 log_sys->mutex 線程凍結 → 實例 Hang

根因:Memory Barrier 導致的鎖喚醒丟失

在釋放 log_flush_order_mutex 時,mutex_reset_lock_word(清鎖標誌)與 mutex_get_waiters(檢查等待者)因 CPU 指令重排(Memory Barrier 缺失)執行順序顛倒:先清標誌後檢等待者,導致檢查時鎖已顯示空閒,錯過喚醒窗口。

解決方案

方案一:移除有問題的判斷邏輯(簡暴)

直接刪除 log_get_lsn 中觸發該路徑的判斷代碼,該代碼僅為開發調試 LSN 異常用,實際 Crash 時也無用。

方案二:改用 log_peek_lsn(優雅,官方採納)


// 原代碼
lsn = log_get_lsn();
// 修改為
lsn = log_peek_lsn();  // 內部先 try_lock,獲取失敗直接返回,不阻塞、不判斷

log_peek_lsn 非阻塞嘗試獲取鎖,失敗即返回,繞過有缺陷的喚醒路徑。

驗證與官方修復確認

  • 修改後版本壓測不再復現
  • 官方 Git 提交記錄確認:MySQL 5.6.28 及之前版本受影響,修復採用方案二
  • Commit Message 明確指出:log_get_lsn 在持有 log_sys->mutex 時調用 log_peek_lsn 會導致死鎖,改為無鎖讀取

總結與啟示

  1. 工具鏈威力:pstack + pt-pmp + gdb 現場三件套,快速從現象定位到代碼行
  2. 鎖設計陷阱:為減少熱點鎖競爭拆分細粒度鎖(log_flush_order_mutex),反引入新的同步複雜度
  3. Memory Barrier 隱患:無鎖編程、多線程同步中,務必成對使用獲取/釋放語義屏障,或直接使用成熟同步原語
  4. 監控線程單點:srv_error_monitor_thread 承擔喚醒補償職責,自身卻依賴業務鎖,形成單點故障鏈
  5. 版本回溯價值:遇到疑難雜症,檢查上游社區是否已修復,往往能節省大量分析時間

這次排查從現象到本質,穿透了現象、鎖、線程、CPU 指令重排四層迷霧,展示了資料庫內核級故障診斷的硬核功力。

copyright © 萬盛學電腦網 all rights reserved