每個 DBA 是不是都有過刪庫的經歷?刪庫了沒有備份怎麽辦?備份恢復後無法啟動服務什麽情況?表定義損壞數據無法讀取怎麽辦??
我曾遇到某初創互聯網企業,因維護人員不規範的備份恢復操作,導致系統表空間文件被初始化,上萬張表無法讀取,花了數小時才搶救回來。
當妳發現數據無法讀取時,也許並非數據丟失了,可能是 DBMS 找不到描述數據的信息。
背景
先來了解下幾張關鍵的 InnoDB 數據字典表,它們保存了部分表定義信息,在我們恢復表結構時需要用到。
SYS_TABLES 描述 InnoDB 表信息CREATE TABLE `SYS_TABLES` (`NAME` varchar(255) NOT NULL DEFAULT '', ?表名`ID` bigint(20) unsigned NOT NULL DEFAULT '0', ?表id`N_COLS` int(10) DEFAULT NULL,`TYPE` int(10) unsigned DEFAULT NULL,`MIX_ID` bigint(20) unsigned DEFAULT NULL,`MIX_LEN` int(10) unsigned DEFAULT NULL,`CLUSTER_NAME` varchar(255) DEFAULT NULL,`SPACE` int(10) unsigned DEFAULT NULL, ? 表空間idPRIMARY KEY (`NAME`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;SYS_INDEXES 描述 InnoDB 索引信息CREATE TABLE `SYS_INDEXES` ( ?`TABLE_ID` bigint(20) unsigned NOT NULL DEFAULT '0', 與sys_tables的id對應 ?`ID` bigint(20) unsigned NOT NULL DEFAULT '0', ?索引id ?`NAME` varchar(120) DEFAULT NULL, 索引名稱 ?`N_FIELDS` int(10) unsigned DEFAULT NULL, 索引包含字段的個數 ?`TYPE` int(10) unsigned DEFAULT NULL, ?`SPACE` int(10) unsigned DEFAULT NULL, ?存儲索引的表空間id ?`PAGE_NO` int(10) unsigned DEFAULT NULL, ?索引的root page id ?PRIMARY KEY (`TABLE_ID`,`ID`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;SYS_COLUMNS 描述 InnoDB 表的字段信息CREATE TABLE `SYS_COLUMNS` ( ?`TABLE_ID` bigint(20) unsigned NOT NULL, 與sys_tables的id對應 ?`POS` int(10) unsigned NOT NULL, 字段相對位置 ?`NAME` varchar(255) DEFAULT NULL, 字段名稱 ?`MTYPE` int(10) unsigned DEFAULT NULL, ?字段編碼 ?`PRTYPE` int(10) unsigned DEFAULT NULL, 字段校驗類型 ?`LEN` int(10) unsigned DEFAULT NULL, ?字段字節長度 ?`PREC` int(10) unsigned DEFAULT NULL, 字段精度 ?PRIMARY KEY (`TABLE_ID`,`POS`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;SYS_FIELDS 描述全部索引的字段列CREATE TABLE `SYS_FIELDS` ( ?`INDEX_ID` bigint(20) unsigned NOT NULL, ?`POS` int(10) unsigned NOT NULL, ?`COL_NAME` varchar(255) DEFAULT NULL, ?PRIMARY KEY (`INDEX_ID`,`POS`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;./storage/innobase/include/dict0boot.h 文件定義了每個字典表的 index id,對應 id 的 page 中存儲著字典表的數據。
這裏我們需要借助 undrop-for-innodb 工具恢復數據,它能讀取表空間信息得到 page,將數據從 page 中提取出來。
# wget /chhabhaiya/undrop-for-innodb/archive/master.zip# yum install -y gcc flex bison# make# make sys_parser
# ./sys_parser 讀取表結構信息
sys_parser [-h] [-u] [-p] [-d] databases/table
stream_parser 讀取 InnoDB page 從 ibdata1 或 ibd 或分區表
# ./stream_parserYou must specify file with -f optionUsage: ./stream_parser -f <innodb_datafile> [-T N:M] [-s size] [-t size] [-V|-g] ?Where: -h - Print this help -V or -g ? - Print debug information -s size - Amount of memory used for disk cache (allowed examples 1G 10M). Default 100M -T - retrieves only pages with index id = NM (N - high word, M - low word of id) -t size - Size of InnoDB tablespace to scan. Use it only if the parser can't determine it by himself.
c_parser 從 innodb page 中讀取記錄保存到文件
# ./c_parserError: Usage: ./c_parser -4|-5|-6 [-dDV] -f <InnoDB page or dir> -t table.sql [-T N:M] [-b <external pages directory>] ?Where -f <InnoDB page(s)> -- InnoDB page or directory with pages(all pages should have same index_id) -t <table.sql> -- CREATE statement of a table -o <file> -- Save dump in this file. Otherwise print to stdout -l <file> -- Save SQL statements in this file. Otherwise print to stderr -h ?-- Print this help -d ?-- Process only those pages which potentially could have deleted records (default = NO) -D ?-- Recover deleted rows only (default = NO) -U ?-- Recover UNdeleted rows only (default = YES) -V ?-- Verbose mode (lots of debug information) -4 ?-- innodb_datafile is in REDUNDANT format -5 ?-- innodb_datafile is in COMPACT format -6 ?-- innodb_datafile is in MySQL 5.6 format -T ?-- retrieves only pages with index id = NM (N - high word, M - low word of id) -b <dir> -- Directory where external pages can be found. Usually it is pages-XXX/FIL_PAGE_TYPE_BLOB/ -i <file> -- Read external pages at their offsets from <file>. -p prefix -- Use prefix for a directory name in LOAD DATA INFILE command
接下來,我們演示場景的幾種數據恢復場景。
場景1:drop table
是否啟用了 innodb_file_per_table 其恢復方法有所差異,當發生誤刪表時,應盡快停止MySQL服務,不要啟動。若 innodb_file_per_table=ON,最好只讀方式重新掛載文件系統,防止其他進程寫入數據覆蓋之前塊設備的數據。
如果評估記錄是否被覆蓋,可以表中某些記錄的作為關鍵字看是否能從 ibdata1 中篩選出。
#?grep WOODYHOFFMAN ibdata1
Binary file ibdata1 matches
也可以使用 bvi(適用於較小文件)或 hexdump -C(適用於較大文件)工具
以表 sakila.actor 為例CREATE TABLE `actor` (`actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,`first_name` varchar(45) NOT NULL,`last_name` varchar(45) NOT NULL,`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`actor_id`),KEY `idx_actor_last_name` (`last_name`)) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8
首先恢復表結構信息1. 解析系統表空間獲取 page 信息
./stream_parser -f /var/lib/mysql/ibdata1
2. 新建壹個 schema,把系統字典表的 DDL 導入
cat dictionary/SYS_* | mysql recovered
3. 創建恢復目錄
mkdir -p dumps/default
4. 解析系統表空間包含的字典表信息,
./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql > dumps/default/SYS_TABLES 2> dumps/default/SYS_TABLES.sql./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000002.page -t dictionary/SYS_COLUMNS.sql > dumps/default/SYS_COLUMNS 2> dumps/default/SYS_COLUMNS.sql./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql > dumps/default/SYS_INDEXES 2> dumps/default/SYS_INDEXES.sql./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000004.page -t dictionary/SYS_FIELDS.sql > dumps/default/SYS_FIELDS 2> dumps/default/SYS_FIELDS.sql
5. 導入恢復的數據字典
cat dumps/default/*.sql | mysql recovered
6. 讀取恢復後的表結構信息
./sys_parser -pmsandbox -d recovered sakila/actor
由於 5.x 版本 innodb 引擎並非完整記錄表結構信息,會丟失 AUTO_INCREMENT 屬性、二級索引和外鍵約束, DECIMAL 精度等信息。
若是 mysql 5.5 版本 frm 文件被從系統刪除,在原目錄下 touch 與原表名相同的 frm 文件,還能讀取表結構信息和數據。若只有 frm 文件,想要獲得表結構信息,可使用 mysqlfrm --diagnostic /path/to/xxx.frm,連接 mysql 會顯示字符集信息。
innodb_file_per_table=OFF
因為是***享表空間模式,數據頁都存儲在 ibdata1,可以從 ibdata1 文件中提取數據。
1. 獲取表的 table id,sys_table 存有表的 table id,sys_table 表 index id 是1,所以從0000000000000001.page 獲取表 id./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql | grep sakila/actor000000000B28 ?2A000001430D4D ?SYS_TABLES ?"sakila/actor" ?158 ?4 ?1 0 ? 0 ? "" ?0000000000B28 ?2A000001430D4D ?SYS_TABLES ?"sakila/actor" ?158 ?4 ?1 0 ? 0 ? "" ?0
2. 利用 table id 獲取表的主鍵 id,sys_indexes 存有表索引信息,innodb 索引組織表,找到主鍵 id 即找到數據,sys_indexes 的 index id 是3,所以從0000000000000003.page 獲取主鍵 id
./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql | grep 158000000000B28 2A000001430BCA ?SYS_INDEXES 158 376 "PRIMARY" ? 1 ? 3 ? 0 ? 4294967295000000000B28 2A000001430C3C ?SYS_INDEXES 158 377 "idx_actor_last_name" 1 ? 0 ? 0 ? 4294967295000000000B28 2A000001430BCA ?SYS_INDEXES 158 376 "PRIMARY" ? 1 ? 3 ? 0 ? 4294967295000000000B28 2A000001430C3C ?SYS_INDEXES 158 377 "idx_actor_last_name" 1 ? 0 ? 0 ? 4294967295
3. 知道了主鍵 id,就可以從對應 page 中提取表數據,並生成 sql 文件。
./c_parser -4f pages-ibdata1/FIL_PAGE_INDEX/0000000000000376.page -t sakila/actor.sql > dumps/default/actor 2> dumps/default/actor_load.sql
4. 最後導入恢復的數據
cat dumps/default/*.sql | mysql sakila
更多詳細情況點擊網頁鏈接
請點擊輸入圖片描述