多實例壓測並發 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 兩處操作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 中調用srv_error_monitor_thread 正嘗試獲取 log_sys->mutex 而阻塞!log_sys->mutex,嘗試獲 log_flush_order_mutex 入 Flush Listlog_flush_order_mutex 時,因 Memory Barrier 導致喚醒指令重排,未喚醒 Thread 446log_flush_order_mutex,持續持有 log_sys->mutexsrv_error_monitor_thread 需獲取 log_sys->mutex 執行喚醒檢查,被 Thread 446 阻塞log_sys->mutex 線程凍結 → 實例 Hang在釋放 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 非阻塞嘗試獲取鎖,失敗即返回,繞過有缺陷的喚醒路徑。
log_get_lsn 在持有 log_sys->mutex 時調用 log_peek_lsn 會導致死鎖,改為無鎖讀取pstack + pt-pmp + gdb 現場三件套,快速從現象定位到代碼行log_flush_order_mutex),反引入新的同步複雜度srv_error_monitor_thread 承擔喚醒補償職責,自身卻依賴業務鎖,形成單點故障鏈這次排查從現象到本質,穿透了現象、鎖、線程、CPU 指令重排四層迷霧,展示了資料庫內核級故障診斷的硬核功力。