萬盛學電腦網

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

MySQL 5.6 物理備份死鎖深度分析:Percona XtraBackup 與複制執行緒衝突案例

前言

在 MySQL 5.6 主從架構下,從庫使用 Percona XtraBackup (PXB) 進行物理備份時,可能觸發備份執行緒、SQL 複制執行緒、使用者執行緒三方死鎖。本文基於實際 Case 深度剖析兩種死鎖場景的形成機制、根因與解決方案,並介紹 SystemTap 復現技術。

PXB 備份流程回顧

  1. 複製 InnoDB Redo Log (獨立執行緒,持續至備份結束)
  2. 複製所有 .ibd 資料檔案
  3. 加全域讀鎖:FLUSH TABLES WITH READ LOCK (FTWRL)
  4. 複製 .frm、.MYD、.MYI 等非 InnoDB 檔案
  5. 獲取位點資訊:SHOW SLAVE STATUS、SHOW MASTER STATUS
  6. 解鎖:UNLOCK TABLES
  7. 收尾處理

FTWRL 會獲取兩個 MDL 鎖:全域讀鎖 (MDL_key::GLOBAL) 與 全域 COMMIT 鎖 (MDL_key::COMMIT)。MyISAM 表多時鎖持有時間長,故建議在從庫備份。

Case 1:備份執行緒與 SQL 執行緒雙方死鎖

現場觀察

mysql> SHOW PROCESSLIST;
+----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+
| Id | User        | Host            | db   | Command | Time | State                                  | Info              |
+----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+
| 1  | root        | 127.0.0.1:53309 | NULL | Query   | 278  | init                                   | show slave status |
| 2  | system user |                 | NULL | Connect | 381  | Queueing master event to the relay log | NULL              |
| 3  | system user |                 | NULL | Connect | 311  | Waiting for commit lock                | NULL              |
| 4  | root        | 127.0.0.1:53312 | NULL | Query   | 0    | init                                   | show processlist  |
+----+-------------+-----------------+------+---------+------+----------------------------------------+-------------------+
  • Id=1:備份執行緒卡在 SHOW SLAVE STATUS
  • Id=3:SQL 執行緒狀態 Waiting for commit lock
  • 後續新連線執行 SHOW SLAVE STATUS 也被阻塞,甚至 KILL 無效

堆疊追蹤

# 備份執行緒
#0 __lll_lock_wait
#1 _L_lock_974
#2 __GI___pthread_mutex_lock
#3 inline_mysql_mutex_lock
#4 show_slave_status          <-- 等待 rli->data_lock
#5 mysql_execute_command

# SQL 執行緒
#0 pthread_cond_timedwait
#1 inline_mysql_cond_timedwait
#2 MDL_wait::timed_wait
#3 MDL_context::acquire_lock  <-- 等待 COMMIT 鎖
#4 ha_commit_trans
#5 trans_commit
#6 Xid_log_event::do_commit

GDB 驗證鎖持有者

(gdb) p (MDL_key::enum_mdl_namespace)lock->key->m_ptr[0]
$24 = MDL_key::COMMIT
(gdb) p ((THD*)lock->m_granted.m_list.m_first->m_ctx->m_owner)->thread_id
$25 = 1  <-- 備份執行緒持有 COMMIT 鎖

死鎖形成鏈

  1. 備份執行緒執行 FTWRL,獲得 COMMIT 鎖
  2. SQL 執行緒處理 Xid event 準備提交事務,請求 COMMIT 鎖,被阻塞
  3. SQL 執行緒持有 rli->data_lock (mutex,非 MDL 系統)
  4. 備份執行緒執行 SHOW SLAVE STATUS 獲取位點,請求 rli->data_lock,被 SQL 執行緒阻塞
  5. 雙方互相持有對方需要的鎖 → 死鎖

為何 MDL 死鎖檢測失效?

MDL 系統只能檢測 MDL 鎖 (如 COMMIT 鎖) 的循環等待。rli->data_lock 是獨立 mutex,不屬於 MDL 體系,MDL 檢測器「看不見」它,故無法發現死鎖。

後續 SHOW SLAVE STATUS 全部卡死原因

SHOW SLAVE STATUS 入口有 LOCK_active_mi mutex 保護,首個死鎖的查詢未退出,後續查詢阻塞於此 mutex,且無法響應 KILL (無法檢測 thd->killed)。

根因與官方修復

SQL 執行緒在 Xid_log_event::do_commit 前持有 rli->data_lock 是多餘的。MySQL 官方在 補丁 中修復:提交前釋放此鎖。影響版本:5.6.21 前。

Case 2:三方死鎖 (備份執行緒 + SQL 執行緒 + 使用者執行緒)

現場觀察

mysql> SHOW PROCESSLIST;
+----+-------------+-----------------+------+---------+------+----------------------------------+-------------------+
| Id | User        | Host            | db   | Command | Time | State                            | Info              |
+----+-------------+-----------------+------+---------+------+----------------------------------+-------------------+
| 2  | system user |                 | NULL | Connect | 436  | Waiting for master to send event | NULL              |
| 3  | system user |                 | NULL | Connect | 157  | Waiting for commit lock          | NULL              |
| 6  | root        | 127.0.0.1:42787 | NULL | Query   | 86   | init                             | show slave status |
| 7  | root        | 127.0.0.1:42788 | NULL | Query   | 96   | Killing slave                    | stop slave        |
| 8  | root        | 127.0.0.1:42789 | NULL | Query   | 0    | init                             | show processlist  |
+----+-------------+-----------------+------+---------+------+----------------------------------+-------------------+

死鎖鏈

  1. 備份執行緒 (Id=6) 執行 FTWRL,持有 COMMIT 鎖
  2. SQL 執行緒 (Id=3) 提交事務請求 COMMIT 鎖,阻塞
  3. 使用者執行緒 (Id=7) 執行 STOP SLAVE,等待 SQL 執行緒退出 (持有 LOCK_active_mi)
  4. 備份執行緒 (Id=6) 執行 SHOW SLAVE STATUS 請求 LOCK_active_mi,被使用者執行緒阻塞
  5. 三方循環等待 → 死鎖

本質判斷

此非代碼 Bug,屬運維操作不當:當 SQL 執行緒處於 Waiting for commit lock 時,嚴禁執行 STOP SLAVE。

死鎖解決方案

緊急處理:KILL SQL 執行緒

因備份/使用者執行緒已無響應,唯有 KILL SQL 執行緒 (Id=3) 打破循環。

風險評估

MySQL 版本KILL SQL 執行緒風險建議
5.6.21 之前高:存在 Bug,KILL 時可能跳過事務導致主從資料不一致KILL 後必須對比主從資料,補齊缺失事務
5.6.21 及之後低:事務可正常回滾重做直接 KILL 安全

死鎖復現技術:SystemTap

無需修改代碼重編譯,利用 SystemTap 動態探針延遲 Xid_log_event::do_commit:

# 列出可探測函數
sudo stap -L 'process("/usr/sbin/mysqld").function("*")'
sudo stap -L 'process("/usr/sbin/mysqld").function("*Xid_log_event::*")'

# 延遲 do_commit 3 秒 (PID 16011 為 mysqld 進程)
sudo stap -v -g -d /usr/bin/mysqld --ldd -e '
    probe process(16011).function("Xid_log_event::do_commit") {
        printf("got it\n")
        mdelay(3000)
    }'

執行後,每當 SQL 執行緒提交事務會暫停 3 秒,給予足夠時間執行 FTWRL 搶佔 COMMIT 鎖,穩定復現死鎖。

總結與預防

  • 升級 MySQL ≥ 5.6.21,修復 Case 1 核心 Bug
  • 監控腳本避開備份窗口:備份期間禁用 SHOW SLAVE STATUS 輪詢、禁止 STOP SLAVE
  • 優化備份策略:大量 MyISAM 表考慮遷移至 InnoDB,縮短 FTWRL 持鎖時間
  • 考慮 XtraBackup --no-lock 選項 (需全 InnoDB 表) 或快照備份,根避 FTWRL
copyright © 萬盛學電腦網 all rights reserved