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

MySQL InnoDB 資料災難恢復實戰:Percona Data Recovery Tool (innodb-tools) 完整操作手冊

     編輯:MySQL 資料庫相關知識

誤執行 DROP/TRUNCATE/DROP DATABASE 後的最後一道防線

當備份失效、Binlog 不全、延遲從庫也被污染時,**Percona Data Recovery Tool (innodb-tools)** 是唯一能從原始 `.ibd` / `ibdata1` 頁面層面提取行記錄的開源工具。本文記錄實戰全流程:編譯、頁面切分、Index ID 定位、表結構映射、行提取、資料導入,附避坑指南。

⚠️ 核心前提 (必讀):

  • ✅ 僅支援 **InnoDB/XtraDB** 表,MyISAM 無效。
  • ✅ **離線操作**:不需 MySQL 運行,**嚴禁在 MySQL 運行時直接複製 ibd/ibdata** (會導致頁面不一致)。
  • ⚠️ **無法保證 100% 恢復**:已被覆寫的頁面永久遺失;碎片化嚴重時行序亂序。
  • ⏰ **黃金時間**:發現資料遺失**立即停寫入、備份原始檔案** (cp -a /var/lib/mysql /backup/$(date +%F))。
  • 🛠️ **半自動流程**:需人工判斷 Index ID、修正表結構、過濾髒資料。
  • 🧠 **業務熟悉度決定成敗**:懂表結構、資料分佈、邊界值,才能從雜訊中識別真實行。

實戰場景:誤 TRUNCATE TABLE customer

mysql> TRUNCATE TABLE customer;

步驟 1:環境準備與編譯

# 依賴:gcc, make, cmake (舊版用 configure)
wget https://launchpad.net/percona-data-recovery-tool-for-innodb/trunk/release-0.5/+download/percona-data-recovery-tool-for-innodb-0.5.tar.gz
tar -zxvf percona-data-recovery-tool-for-innodb-0.5.tar.gz
cd percona-data-recovery-tool-for-innodb-0.5

# 配置 (引用 MySQL 源碼內建 headers)
cd mysql-source
./configure
cd ..

# 編譯產出 page_parser、constraints_parser
make

產出:`page_parser` (頁面切分)、`constraints_parser` (行提取,需二次編譯注入表結構)。

步驟 2:頁面切分

InnoDB 預設頁面 16KB。依 `innodb_file_per_table` 分兩種情況:

情況 A:file_per_table=ON (獨立 .ibd)

通常直接用 `.ibd` 即可,但若表有多個索引 (主鍵+二級索引),仍建議切分以隔離主鍵頁面。

# MySQL 5.0+ (COMPACT 格式)
./page_parser -5 -f /path/to/table.ibd
# MySQL 5.0 前 (REDUNDANT 格式)
./page_parser -4 -f /path/to/table.ibd

情況 B:file_per_table=OFF (共享 ibdata1)

**必須切分**,從巨大 `ibdata1` 中按 Index ID 拆出目標表頁面。

./page_parser -5 -f /var/lib/mysql/ibdata1

產出目錄結構:`pages-/FIL_PAGE_INDEX//.page`

步驟 3:定位目標 Index ID (關鍵)

主鍵 (PRIMARY) 包含完整行記錄,優先恢復主鍵 Index ID。

方法 1:InnoDB Tablespace Monitor (官方標準,需 MySQL 運行)

CREATE TABLE innodb_table_monitor (id INT) ENGINE=InnoDB;
-- 觀察 error log 輸出
-- 範例輸出:
-- TABLE: name test/customer, id 0 142, ...
-- INDEX: name PRIMARY, id 0 286, fields 1/11, type 3
--          root page 50, ...
-- 主鍵 Index ID = 0-286
DROP TABLE innodb_table_monitor;

方法 2:全量恢復法 (本文實戰採用,無需 MySQL 運行)

直接處理 `page_parser` 產出的**所有 Index ID 目錄**,後續用 `constraints_parser` 配合表結構自動過濾。實測可行,省去啟動 MySQL 步驟。

步驟 4:建立表結構定義 (C Struct) 並編譯 constraints_parser

最簡單方式:`create_defs.pl` 連線 MySQL 讀取 `SHOW CREATE TABLE` 自動生成。

# 需有一個可連線的 MySQL (結構相同即可,資料可空)
./create_defs.pl --host=localhost --user=root --password=xxx --db=test --table=customer > include/table_defs.h

# 根據生成的 table_defs.h 重新編譯 constraints_parser
make

生成的 C 結構體範例片段

table_def_t table_definitions[] = {
  {
    name: "customer",
    columns: {
      { name: "customer_id", type: FT_UINT, fixed_length: 2, has_limits: TRUE, limits: { uint_min_val: 0, uint_max_val: 65535 }, can_be_null: FALSE },
      { name: "DB_TRX_ID", type: FT_INTERNAL, fixed_length: 6, can_be_null: FALSE },  -- InnoDB 隱藏列
      { name: "DB_ROLL_PTR", type: FT_INTERNAL, fixed_length: 7, can_be_null: FALSE }, -- InnoDB 隱藏列
      { name: "store_id", type: FT_UINT, fixed_length: 1, ... },
      { name: "first_name", type: FT_CHAR, min_length: 0, max_length: 45, ... },
      ...
      { type: FT_NONE }  -- 結束標記
    }
  }
};

**隱藏列 `DB_TRX_ID`、`DB_ROLL_PTR` 必須保留**,位置順序需與 InnoDB 行格式一致 (緊接主鍵之後)。若手工編寫,極易出錯,強烈建議用 `create_defs.pl` 自動生成。

步驟 5:合併頁面檔案

# 將目標 Index ID 目錄下所有 .page 依頁號排序合併
find pages-1246363747/0-286/ -name '*.page' -type f | sort -n | xargs cat > pages-1246363747/0-286/customer_pages_concatenated

步驟 6:運行 constraints_parser 提取行記錄

# 單頁測試
./constraints_parser -5 -f pages-1246363747/0-286/50-00000050.page

# 全量合併檔案
./constraints_parser -5 -f pages-1246363747/0-286/customer_pages_concatenated > /tmp/customer_recovered.tsv

輸出格式 (Tab 分隔,首欄表名)

customer	1	1	"PATRICIA"	"JOHNSON"	"PATRICIA.JOHNSON@..."	6	1	"2006-02-14 22:04:36"	1140008240
customer	2	1	"LINDA"	"WILLIAMS"	"LINDA.WILLIAMS@..."	7	1	"2006-02-14 22:04:36"	1140008240
... (夾雜大量全 0/空值/亂碼的髒行) ...

步驟 7:人工/腳本過濾髒資料並導入

輸出包含:正確行、舊版本行 (MVCC)、未提交事務行、已刪除標記行、頁面碎片亂碼。需根據業務特徵過濾:

  • 主鍵 `customer_id` > 0 且遞增。
  • 非空欄位 (`store_id`, `first_name`, `last_name`, `create_date`) 非空/符合格式。
  • `last_update` (Unix timestamp) 合理範圍。
  • 去重:同主鍵保留 `last_update` 最大者 (最新版本)。

導入命令

LOAD DATA INFILE '/tmp/customer_clean.tsv'
REPLACE INTO TABLE customer
FIELDS TERMINATED BY '\t'
OPTIONALLY ENCLOSED BY '\"'
LINES STARTING BY 'customer\t'
(customer_id, store_id, first_name, last_name, email,
 address_id, active, create_date, @last_update)
SET last_update = FROM_UNIXTIME(@last_update);

避坑與優化指南

問題原因對策
編譯失敗缺 headersMySQL 源碼版本不匹配用相同大版本源碼 (如 5.7.33 恢復 5.7.x);或安裝 `libmysqlclient-dev`
page_parser 產出空目錄格式參數錯誤 (-4/-5)、檔案非 InnoDB確認 `innodb_file_format` (Barracuda=COMPACT/COMPRESSED 用 -5;Antelope=REDUNDANT 用 -4)
constraints_parser 全是髒資料表結構定義錯誤 (欄位順序/類型/長度不符)嚴格核對 `SHOW CREATE TABLE`、隱藏列順序、CHARSET (utf8mb4 影響 FT_CHAR 長度)
恢復行數遠少於預期頁面已被覆寫、碎片化嚴重、非主鍵索引頁嘗試恢復二級索引頁 (需手工拼接主鍵);接受部分遺失
大表合併頁面極慢海量 .page 檔案分批處理、並行 `constraints_parser`、SSD 存儲

總結

InnoDB 災難恢復是**與時間賽跑、與覆寫博弈**的過程。核心鐵律:

  1. 立即停寫、冷備份原始檔案 (cp -a / LVM snapshot / 存儲快照)。
  2. 離線環境編譯 innodb-tools,版本嚴格對齊。
  3. 主鍵 Index ID 為核心目標,配合 `create_defs.pl` 自動生成精確表結構。
  4. 接受不完美:恢復率 90%+ 即為勝利,餘下靠業務補錄/日誌重放。

平時多演練、備份驗證、延遲從庫保護,才是真正的「免疫系統」。

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