(轉)MySQL InnoDB修復筆記


轉自:https://xuanwobbs.com.cn/archives/2017-02/mysql-innodb-frm-ibd-repair.html

 

容我先說一句:千萬不要直接拷貝數據庫data目錄備份  千萬不要直接拷貝數據庫data目錄備份  千萬不要直接拷貝數據庫data目錄備份(重要的事情重復三遍)

當然如果你是從搜索引擎搜到這篇文章的,恐怕上面那句話也沒什么用了.

某人丟了一個用r1soft直接備份data目錄數據庫名文件夾的WordPress數據庫備份文件給我(不含ibdata和log只有一個數據庫名目錄),問有沒有救,我一打開...滿眼的frm和ibd文件 差點沒昏過去...

照經驗 InnoDB這么直接復制粘貼要蛋疼死,但據說數據很重要讓我想盡辦法修。。沒法子硬着頭皮上吧

閱讀本文需要一定運維知識

需要frm和ibd文件,frm文件用於提取表定義 (CREATE TABLE語句) 。frm文件丟失要有備份的表定義,如果這個也沒有,至少必須有ibdata1

第一階段:使用自動化工具讓數據庫“接受”frm和ibd文件

此階段需要innodb_file_per_table=1之設定,也就是獨立表空間(File-Per-Table tablespace)。共享表空間請直接跳轉第二階段對應段落

MySQL之所以無法像MyISAM直接讀取獨立表空間的tablespace,是因為InnoDB內部維護了一個Table counter,如果表的tablespace id與數據庫內部的counter不合,啟動會報錯並且 innodb_force_recovery也無效, 傳統解決方式我會在本文的雜記提及。 (MYSQL5.6后不會再有此錯誤)

這里先用來自zcgonvh的工具(在此表示感謝 減少了很多工作量)進行批量導入。備份下載 密碼zcgonvh

此工具需要Windows環境 .net4.x版本、MySQL5.6的最新版本。需要一個專用於修復的環境否則可能損壞現有數據庫!

工具使用方式:

InnoDBRestore <username> <password> <port> <srcdir> <destDB>

例如

InnoDBRestore root pass 3306 c:\dbcopy my_database

祝好運吧。如果運氣好,無報錯, c:\dbcopy 下的MyISAM和InnoDB數據都會被導入 my_database(my_database不需要提前創建)。

然而你可能會和我一樣,遇上了數據錯誤(天知道為什么),導入過程可能會報錯。這些報錯很可能是連接被關閉

restoring : wp_comments.frm

unknown error:MySql.Data.MySqlClient.MySqlException (0x80004005): Fatal error encountered during command execution. ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Fatal error encountered attempting to read the resultset. ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Reading from the stream has failed. ---> System.IO.IOException: 無法從傳輸連接中讀取數據: 遠程主機強迫關閉了一個現有的連接。。 ---> System.Net.Sockets.SocketException: 遠程主機強迫關閉了一個現有的連接。

然后同時,查看數據庫的err日志,可能會有類似以下報錯:

InnoDB: Error: trying to access page number 1372160 in space 1,
InnoDB: space name recovery1/wp_comments,
InnoDB: which is outside the tablespace bounds.
InnoDB: Byte offset 0, len 16384, i/o type 10.
InnoDB: If you get this error at mysqld startup, please check that
InnoDB: your my.cnf matches the ibdata files that you have in the
InnoDB: MySQL server.

由於InnoDB引擎遇上了異常,MySQL崩潰退出,導致連接斷開。這種情況下,到MySQL的data目錄下,刪除剛才導入的數據庫的文件夾以及ib_logfile0、ib_logfile1、ibdata1(也就是重置所有InnoDB引擎相關數據)。然后再啟動MySQL。

將引發故障的表文件(ibd、frm)單獨移出來,留作階段2修復使用,再次執行InnoDBRestore,如再遇上故障重復以上步驟,直到工具不再報錯為止。

使用mysqldump將表導出來。建議添加--skip-extended-insert參數以便數據檢查,如果上面一切都順利,無論是導入還是導出都沒有任何報錯,也需要仔細檢查恢復出來的數據是否有異常(很大的負數、數據參雜亂碼、不合理的日期等),有些情況下會有隱性損壞情況。如果沒有,那么恭喜你數據就恢復完成了,不需要繼續向下閱讀了

......然而你可能像我一樣,導出時再次遇上相似故障

ERROR 2013 (HY000): Lost connection to MySQL server during query

查看err日志后,發現另一個原先看起來成功導入的ibd文件dump時由於數據錯誤也失敗了,修改my.cnf,添加innodb_force_recovery=6。然后再重新啟動,再次嘗試dump查看是否成功,如成功需要仔細檢查是否數據正確。如果連 innodb_force_recovery=6 也無法獲得正確數據的話,只能跳過出故障的表,將其余正常的表導出。出故障的表通過第二階段進行修復。

第二階段:修復異常的表

需要環境:Linux【此處使用CentOS6】、MySQL5.6+、編譯套件、undrop-for-innodb

本步驟均以 wp_comments.ibd為例

安裝undrop-for-innodb工具,只需要執行make命令進行編譯,很簡單也非常快。

該工具可用於很多 InnoDB 災難性數據丟失場景的數據庫救援。救援的意思是盡量恢復數據,通常需要這個工具的場合都是很糟糕的,運氣好的情況下你或許能全部提取出。因此無論如何依然不能直接拷貝InnoDB數據庫。p.s.今年1月此工具停止進一步開發了,很可惜

make編譯后會在其目錄生成以下可執行工具:

c_parser
innochecksum_changer
stream_parser

1.使用

./stream_parser -f wp_comments.ibd

拆出ibd文件結構

2.使用mysqlfrm拆出包含表結構的CREATE TABLE語句,在第一階段 zcgonvh的工具里有一個Windows的MySqlFrm.exe亦可使用,這里以該工具為例。Linux的 mysqlfrm可以參考下面本文后雜記

mysqlfrm <username> <password> <port> <srcdir>
例如:
mysqlfrm root pass 3306 c:\dbcopy

會在同目錄下對每個frm文件生成一個.sql文件 內含創建表語句。注意該工具生成的CREATE TABLE語句不含分號,會對之后操作造成影響,需要在語句末尾添加一個分號

3.拆出的ibd文件結構會存儲在pages-wp_comments.ibd里。包含以下子目錄:

FIL_PAGE_INDEX:一般PAGE,依照其ID存放

FIL_PAGE_TYPE_BLOB: 如果遇上較大的數據(例如comments里有text類型的數據並且內容較多),InnoDB會使用BLOB類PAGE存儲數據。需檢查此目錄是否有文件。如有,說明此表使用了BLOB,之后提取命令需要用-b參數指定此目錄進行提取

這里有個問題,很顯然我的情況沒有原數據庫的ibdata1,無從知曉主鍵index_id(該表主鍵索引的PAGE ID,存儲於ibdata1的SYS_INDEXES內),因此只能瞎蒙主鍵所在的PAGE。

↓↓↓↓↓↓↓↓ 以下步驟假設你有 ibdata1 如沒有請跳過 ↓↓↓↓↓↓↓↓

按照步驟1拆分ibdata1,然后編輯recover_dictionary.sh腳本里的mysql命令行 加入用戶名密碼(不然有可能腳本導出數據后腳本也無法將數據導回數據庫)。然后執行此腳本,會將SYS系列表導入test數據庫

使用mysql命令行進入test數據庫后,執行:

mysql> select * from SYS_TABLES where NAME like "%/wp_comments";

+-----------------------+----+--------+------+--------+---------+--------------+-------+
| NAME | ID | N_COLS | TYPE | MIX_ID | MIX_LEN | CLUSTER_NAME | SPACE |
+-----------------------+----+--------+------+--------+---------+--------------+-------+
| recovery1/wp_comments | 16 | 15 | 1 | 0 | 80 | | 2 |
+-----------------------+----+--------+------+--------+---------+--------------+-------+
1 row in set (0.00 sec)

此table的ID為16,然后執行

mysql>  SELECT * FROM SYS_INDEXES where table_id=16;

+----------+----+---------------------------+----------+------+-------+---------+
| TABLE_ID | ID | NAME | N_FIELDS | TYPE | SPACE | PAGE_NO |
+----------+----+---------------------------+----------+------+-------+---------+
| 16 | 22 | PRIMARY | 1 | 3 | 2 | 3 |
| 16 | 23 | comment_post_ID | 1 | 0 | 2 | 4 |
| 16 | 24 | comment_approved_date_gmt | 2 | 0 | 2 | 5 |
| 16 | 25 | comment_date_gmt | 1 | 0 | 2 | 6 |
| 16 | 26 | comment_parent | 1 | 0 | 2 | 7 |
| 16 | 27 | comment_author_email | 1 | 0 | 2 | 8 |
+----------+----+---------------------------+----------+------+-------+---------+
6 rows in set (0.00 sec)

即可獲得主鍵的index_id為22,因此對應的page是:pages-wp_comments.ibd/FIL_PAGE_INDEX/0000000000000022.page。

注:如果是只有ibdata1(共享表空間)的情況,下文的數據提取則直接從pages-ibdata1/FIL_PAGE_INDEX/0000000000000022.page提取即可

↑↑↑↑↑↑↑↑ 以上步驟假設你有 ibdata1 如沒有請跳過 ↑↑↑↑↑↑↑↑

可是我沒有上面的步驟是數據恢復以后模擬的,並非實際情況只好一個個page試,使用c_parser命令對每個page嘗試提取。此表數據結構是COMPACT,因而使用參數-5。如果是MYSQL5.6以上的格式用-6。不確定的話5和6都試下

本例BLOB目錄下有文件,需要-b參數指定BLOB目錄以確保數據完整。

步驟2得到的表結構定義(CREATE TABLE)放在 wp_comments.create.sql

將輸出指向到less以便閱讀:

[root@Test undrop-for-innodb]#./c_parser -5f ./pages-wp_comments.ibd/FIL_PAGE_INDEX/XXXXX.page -b ./pages-wp_comments.ibd/FIL_PAGE_TYPE_BLOB/ -t ./wp_comments.create.sql | less

XXXXX替換成具體的page編號,建議從頭開始嘗試(對兩個有類似問題的表修復的結果似乎暗示第一個的成功概率最高),直到獲得了明顯正確的結果(日期正確 大部分數據正常 Records list為Valid

c_parser會將tsv數據dump到標准輸出管道,並且很貼心的將對應數據恢復SQL命令特意單獨輸出到了錯誤輸出(某個N年未更新的工具還需要自行構建命令導入)。可以使用以下命令對含有正確數據索引的page做最終導出:

./c_parser -5f pages-wp_comments.ibd/FIL_PAGE_INDEX/0000000000224178.page -b pages-wp_comments.ibd/FIL_PAGE_TYPE_BLOB/ -t wp_comments.create.sql > wp_comments 2> wp_comments.sql

會得到

wp_comments:提取出來的tsv格式數據
wp_comments.sql:將tsv導入數據庫的SQL命令

兩個文件。我們需要將這兩個文件挪到/tmp文件夾(規避權限問題),然后修改 wp_comments.sql 內的LOAD DATA LOCAL INFILE路徑,使之符合新的tsv文件路徑。

之后要做的就很簡單了,在一個數據庫用CREATE TABLE語句創建一個同名同定義的表,然后SOURCE /tmp/wp_comments.sql;就能將數據導進數據庫了。

最后dump出來,就是標准的SQL格式了。

雜記

以下只是探索階段的一些沒啥用的記錄,可以直接無視。有興趣可以看看

第一階段使用傳統方式導入tablespace的方法:

環境要求: Linux【此處使用CentOS6】、MySQL5.6+、hexdump 需要專用於修復的環境

wp_commentmeta數據恢復為例,有原生方式簡單方式通過frm文件獲取表定義,選一即可

========原生方式獲取表定義========

創建數據庫略過,用以下命令創建同名的新表:

CREATE TABLE wp_commentmeta (id int(11) NOT NULL ) ENGINE=InnoDB ;

關閉數據庫

用備份里的frm文件替換mysql data目錄剛才新建的frm。添加 innodb_force_recovery=6到my.cnf

啟動數據庫,進入剛才新建的數據庫,執行

show tables;

幾乎可以肯定看不到任何表,此時查看mysql錯誤日志 會有以下信息

InnoDB: table recovery1/wp_commentmeta contains 1 user defined columns in InnoDB, but 4 columns in MySQL. Please check INFORMATION_SCHEMA.INNODB_SYS_COLUMNS and http://dev.mysql.com/doc/refman/5.6/en/innodb-troubleshooting.html for how to resolve it

意思是有4個 columns而我們只定義了1個。在恢復數據庫內drop掉 wp_commentmeta。再關閉數據庫 去掉 innodb_force_recovery,再啟動

再創建一個有4個 columns的 wp_commentmeta:

CREATE TABLE ` wp_commentmeta `(
`weiboid` bigint(20),
`weiboid2` bigint(20),
`weiboid3` bigint(20),
`weiboid4` bigint(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

關閉MySQL,再次用備份里的frm文件替換mysql data目錄剛才新建的frm ,添加 innodb_force_recovery=6,再啟動,進入數據庫,使用SHOW CREATE TABLE  wp_commentmeta;即可獲得表定義。之后即可drop掉這個表然后去掉 innodb_force_recovery再啟動

===========簡單方式=============

使用mysqlfrm(需要額外安裝) 設置的port必須沒有被占用

mysqlfrm --server=root:pass@localhost:3306 --port 3307  /路徑/到/wp_commentmeta.frm

===============================

使用hexdump獲取table id

hexdump -C wp_commentmeta.ibd |head -n 3 |tail -n 1|awk '{print $6$7}'

ID會是16進制,轉換成10進制即可。我獲得了1b79 轉換后得到7033

如果使用原生方式,由於創建過兩次表(-2),然后又要騰出占位空間(-1),需要創建7030個表去“堆”Table counter:

for i in `seq 1 7030`; do mysql -u用戶 -p密碼 數據庫 -e "CREATE TABLE iinser$i (id bigint(20) NOT NULL AUTO_INCREMENT,PRIMARY KEY (id)) ENGINE=innodb "; done

如果使用簡單方式,只需要騰出占位空間(-1),因此需要創建7032個表去 “堆”Table counter:

for i in `seq 1 7032`; do mysql -u用戶 -p密碼 數據庫 -e "CREATE TABLE iinser$i (id bigint(20) NOT NULL AUTO_INCREMENT,PRIMARY KEY (id)) ENGINE=innodb "; done

可能會有在命令行使用-p指定密碼不安全的錯誤提示,無視即可(刪除的步驟僅在MYSQL5.5及以下需要使用)

命令執行完后,還暫時不需要修改recovery模式,使用剛才獲取的表定義,創建同名表:

CREATE TABLE `wp_commentmeta` (
`meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`comment_id` bigint(20) unsigned NOT NULL DEFAULT '0',
`meta_key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`meta_value` longtext COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`meta_id`),
KEY `comment_id` (`comment_id`),
KEY `meta_key` (`meta_key`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

然后丟棄掉這個空白表的tablespace:

alter table wp_commentmeta discard tablespace;

將 wp_commentmeta.ibd文件復制到對應的mysql data目錄下的數據庫文件夾,然后import:

alter table wp_commentmeta import tablespace;

修改my.cnf 添加 innodb_force_recovery=6,重啟mysql,即可將該表數據dump。如果要dump其他表,刪除ib_logfile0、ib_logfile1、ibdata1以及剛才創建的臨時數據庫文件夾,然后重復上述步驟“即可”(不得不說實在是太太太太麻煩死了)

另外需要特別注意的是:這個方式與之前介紹的簡便方式,得到的效果是完全一樣的。

也就是說,如果ibd文件數據庫數據異常無法讀取,這個傳統方式數據庫引擎依然會崩潰報錯,報錯完完全全一樣。這個原始做法寫在這里只是為了記錄

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM