前言
在日常維運中,MySQL 的數據庫可能因各種原因遭到損壞,例如硬碟故障、斷電等。最近工作上就遇到客戶環境出現異常的現象,資料庫疑似寫入壞資料導致 MySQL 不斷跳出此錯誤
Operation cannot be performed. The table 'xxx' is missing, corrupt or contains bad data.
當下處理時還一頭霧水,因為根本無法查詢資料,SELECT 資料時,該資料表都會跳出此錯誤訊息,連資料都無法 query,又該怎麼知道哪裡有壞資料呢?
Forcing InnoDB Recovery
針對這種棘手的情況 MySQL 提供了 InnoDB 強制恢復模式(InnoDB Force Recovery) 來協助修復損壞數據。此模式允許管理人員在無法啟動的情況下訪問資料並嘗試進行修復。
MySQL 設定檔
以下是用 MySQL 5.7.30 的環境
具體操作如下:
首先,要先找到 MySQL 的設定檔 (my.cnf),通常會位於底下幾種路徑 (Docker container 也是一樣)
- /etc/my.cnf
- /etc/mysql/my.cnf
- $MYSQL_HOME/my.cnf
- [datadir]/my.cnf
- ~/.my.cnf
如果都找不到的話,可以執行 mysql --help
,從輸出的 log 中去找 my.cnf 可能的檔案路徑
Default options are read from the following files in the given order: /etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
找到 my.cnf 檔之後,在裡面添加下面這兩行
[mysqld]
innodb_force_recovery = 1
以上設定 1 只是範例,具體要設定多少呢?
InnoDB Force Recovery 參數
innodb_force_recovery 這個參數提供了 0 到 6 不同恢復的級別,以控制恢復的深度
整體來說,愈大的值會包含前面所有小於它的值的影響
- 1-3:允許讀取 table 內的資料,避免 redo 和 rollback 的操作
- 4-6:用於更嚴重損壞的情況,會禁用部分資料處理,並有資料丟失的風險
分別解釋:
1 (SRV_FORCE_IGNORE_CORRUPT)
- 忽略檢查到的損壞 page,儘管已經檢測到了損壞的 page,但仍允許 MySQL 伺服器繼續運行
- 適合在允許伺服器在找到損壞的頁面時運行
2 (SRV_FORCE_NO_BACKGROUND)
- 防止 MySQL 伺服器內的背景程序 (master thread 和 purge threads) 運行
- 如果資料損毀是發生在 purge 階段 (可理解為資料庫世界的 Garbage Collection),則可以設定此值來修正
3 (SRV_FORCE_NO_TRX_UNDO)
- 當 crash 修正後不執行 transaction 的 rollback
4 (SRV_FORCE_NO_IBUF_MERGE)
- 不執行 insert buffer 的操作,設定此值後,有可能會永久損壞資料
- 該模式下,InnoDB 是 Read-only
5 (SRV_FORCE_NO_UNDO_LOG_SCAN)
- 啟動資料庫時不查看 redo log,InnoDB 會將未完成的事務視為已提交
- 因此有可能會永久損壞資料
- 該模式下,InnoDB 是 Read-only
6 (SRV_FORCE_NO_LOG_REDO)
- 不執行與恢復期間相關的 redo log 的 roll-forward
- 恢復後任何恢復前的後續改動皆不執行,這會導致資料庫的 page 處於過時的狀態,並有可能帶給 B tree 和其他資料結構的損壞
- 該模式下,InnoDB 是 Read-only
Best Practice
一般來說都會先將 innodb_force_recovery
設定為 1,再把資料庫 dump 出來,到另一個測試環境做檢測,確定找到問題後,再將髒資料清除還原到生產環境上
值得一提的是,MySQL 文件有說請在緊急狀況的時候,再設定大於 0 的 innodb_force_recovery
,因為此時 MySQL 不允許任何 INSERT
, UPDATE
, DELETE
的操作
如果設定大於 4 以上的 innodb_force_recovery
將有可能導致資料庫損壞,所以請務必在確實備份且測試沒問題之後,再到正式環境上重啟 MySQL。
以上步驟如果還是覺得很混亂的話,建議將 innodb_force_recovery
從 1 開始設定,並逐一增加,直到資料損壞問題被解決
結論
MySQL 的 InnoDB 強制恢復模式為資料庫管理員提供了一個臨時的修復選項,能在資料庫損壞時允許讀取與導出資料。雖然有助於快速恢復重要數據,但它僅適合在緊急情況下使用,並需謹慎選擇適當的恢復級別,以減少數據損失風險。完成修復後,應立即恢復正常設定並進行全面備份,以確保資料安全
至於這次客戶環境為何會有壞資料存在,在一系列排查後發現這跟產品另一個備份服務有關,該服務會在每天午夜定期備份,但因為 mysqldump 未加上 --single-transaction
這個參數,導致備份資料量大的表時,鎖表鎖太久,進而導致資料寫入不完全或有問題,希望之後可以記得這次血淋淋的教訓