首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

MySQL Redo Log 死鎖問題深度排查:log_sys mutex 爭用與 memory barrier 導致實例 Hang 根因分析

     編輯:MySQL 資料庫相關知識

一、 故障現象

  • 並發 384 線程壓測時 MySQL 實例 Hang 住
  • 所有事務相關語句(含簡單 SELECT)無法執行
  • 所有線程處於等待狀態,KILL 無效
  • 原生版本正常,新版本引入問題

二、 現場堆棧採集與分類

pstack `pidof mysqld` > hang.info
pt-pmp hang.info  # 統計線程分布

線程分類:

  1. 等待進入 InnoDB 引擎層(innodb_thread_concurrency=16 限制)
  2. 需寫 redo log 的後台線程
  3. Purge 線程、Master 線程
  4. 大量需寫 redo log 的用戶線程 → 關鍵突破口

三、 核心鎖爭用鏈條

用戶線程 → 需 log_sys->mutex → 寫 redo log buffer
                ↓
         將髒頁加入 flush_list 需 log_sys->log_flush_order_mutex
                ↓
         log_flush_order_mutex 被佔用 → 所有需 log_sys->mutex 線程阻塞
                ↓
         srv_error_monitor_thread 也需 log_sys->mutex → 無法執行死鎖檢測/喚醒
                ↓
         整個實例 Hang

四、 關鍵發現:log_flush_order_mutex 狀態異常

  • GDB 附掛檢查:log_sys->log_flush_order_mutex = {waiters=1, lock_word=0}
  • 即:鎖空閒但有等待者,等待線程未被喚醒
  • 正常流程:持有者釋放鎖時調用 mutex_exit_func → sync_arr_wake_threads_if_sema_free 喚醒等待者

五、 根因定位:Memory Barrier 導致喚醒信號丟失

// 源碼註釋關鍵揭示
// Due to memory barriers, the order of mutex_get_waiters() and mutex_reset_lock_word()
// may be reversed during execution, causing hang.
// Special function sync_arr_wake_threads_if_sema_free() written to mitigate this.

執行順序:

  1. 線程 A 持有 log_flush_order_mutex
  2. 線程 B 嘗試獲取,進入等待隊列(waiters=1)
  3. 線程 A 釋放鎖:mutex_reset_lock_word() 先於 mutex_get_waiters() 執行(CPU 重排序)
  4. 鎖標記為空閒,但等待者計數未清零 → sync_arr_wake_threads_if_sema_free 判斷「鎖空閒且無等待者」→ 不喚醒
  5. 線程 B 永久等待,持有 log_sys->mutex,阻塞所有後續線程

六、 連環死鎖閉環

  1. 用戶線程 T1 獲取 log_sys->mutex,等待 log_flush_order_mutex(未被喚醒)
  2. 後台線程 T2(srv_error_monitor)嘗試獲取 log_sys->mutex 阻塞
  3. T2 職責是定期調用 sync_arr_wake_threads_if_sema_free 修復 memory barrier 導致的假死
  4. T2 自己被阻塞 → 修復機制失效 → T1 永久等待 → 實例全面 Hang

七、 官方修復方案(MySQL 5.6.29+ / 5.7.11+)

// 原代碼:log_get_lsn() 直接獲取 log_sys->mutex 讀取 LSN
// 修復:改為 log_peek_lsn() 嘗試非阻塞獲取
lsn = log_peek_lsn();  // 內部 mutex_enter_nowait() 失敗即返回,不阻塞

// log_peek_lsn 實現
ulint log_peek_lsn(void) {
    if (mutex_enter_nowait(&log_sys->mutex)) return log_sys->lsn;
    mutex_exit(&log_sys->mutex);
    return 0;  // 獲取失敗返回 0,調用方容忍
}

核心思想:避免在死鎖檢測/監控路徑上獲取熱點鎖,寧可讀不到最新 LSN 也不阻塞。

八、 影響版本與升級建議

  • 受影響:MySQL 5.6.28 及之前、5.7.10 及之前
  • 修復版本:5.6.29、5.7.11、8.0.1+
  • 臨時規避:增大 innodb_thread_concurrency、降低並發壓測強度、避免長事務

九、 總結與避坑心法

  1. 熱點鎖監控:關注 log_sys->mutex、log_flush_order_mutex 等核心鎖等待情況
  2. 死鎖檢測線程保護:監控線程不應阻塞在業務熱點鎖上
  3. Memory Barrier 意識:多線程釋放鎖喚醒邏輯需成對內存屏障,參考 sync_arr_wake_threads_if_sema_free 模式
  4. 版本跟進:核心存儲引擎 Bug 修復往往在小版本,保持 5.6/5.7/8.0 最新 GA 版本

此案例是 MySQL 內核並發控制經典教材,展示了「微小的 CPU 重排序 → 鎖喚醒失效 → 監控線程阻塞 → 全實例 Hang」的多米諾效應。讀懂源碼註釋,往往能直達問題本質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved