Percona Data Recovery Tool 單表恢復


前幾天寫過update或者delete忘加where條件的數據恢復。今天介紹一款開源的MySQL數據庫InnoDB數據恢復工具:innodb-tools,它通過從原始數據文件中提取表的行記錄,實現從丟失的或者被毀壞的MySQL表中恢復數據。例如,當你不小心執行DROP TABLE、TRUNCATE TABLE之后,可以通過以下方式恢復數據。

在介紹innodb-tools工具進行數據恢復之前,首先明確以下幾點:

1、這個工具只能對InnoDB/XtraDB表有效,而無法恢復MyISAM表

2、這個工具是以保存的MySQL數據文件進行恢復的,而不用MySQL Server運行。

3、不能保證數據總一定可被恢復。例如,被重寫的數據不能被恢復,這種情況下可能需要針對系統或物理的方式來恢復,不屬於本工具的范疇。

4、恢復的最好時機是當你發現數據丟失時,盡快備份MySQL數據文件。

5、使用這個工具需要手動做一些工作,並不是全自動完成的。

6、恢復過程依賴於你對丟失數據的了解程度,在恢復過程中可能需要在不同版本的數據之間做出選擇。那么如果你越了解自己的數據,恢復的可能性就越大。

接下來,下面通過一個例子來介紹如何通過這個工具進行恢復。

1. 前提條件

首先,需要理解的是innodb-tools工具不是通過連接到在線的database進行數據恢復,而是通過離線拷貝數據的方式進行的。注意:不要在MySQL運行的時候,直接拷貝InnoDB文件,這樣是不安全的,會影響數據恢復過程。

為了完成數據恢復,必須知道將要被恢復的表結構(列名、數據類型)。最簡單的方式就是SHOW CREATE TABLE,當然后續會介紹幾種可替代的方式。因此,如果有一個MySQL server作為備份,即使數據是很早的甚至表中沒有記錄,可以有助於使用innodb-tools工具進行恢復。不過這個不是必須的。

2.簡單例子

mysql> use book;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select count(*) from million_words;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.13 sec)

mysql> truncate million_words;
Query OK, 0 rows affected (0.23 sec)

mysql> 

3.構建工具

1、下載解壓innodb-tools工具源碼:

安裝依賴,否則拋出如下錯誤:

/usr/bin/ld: cannot find -lrt
collect2: ld returned 1 exit 
status
make: *** [page_parser] Error 1
[root@localhost ~]# yum install glibc-static -y
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 -xvf percona-data-recovery-tool-for-innodb-0.5.tar.gz -C /usr/local/
cd /usr/local
ln -s percona-data-recovery-tool-for-innodb-0.5 percona-data-recovery-tool

2、進入解壓后根目錄下的mysql-source目錄,運行配置命令(注:不運行make命令):

[root@localhost mysql-source]# cd /usr/local/percona-data-recovery-tool/mysql-source/
[root@localhost mysql-source]# ./configure 

3、完成配置步驟后,回到解壓后的根目錄,運行make命令,編譯生成page_parserconstraints_parser工具

[root@localhost mysql-source]# cd ..
[root@localhost percona-data-recovery-tool]# make

page_parser工具將根據InnoDB的底層實現原理,解析表的頁和行結構。constraints_parser工具暫時不使用,后續還需要在定義表結構之后,重新編譯生成它。

4. 提取需要的頁

InnoDB頁的默認大小是16K,每個頁屬於一個特定表中的一個特定的index。page_parser工具通過讀取數據文件,根據頁頭中的index ID,拷貝每個頁到一個單獨的文件中。

如果啟用了innodb_file_per_table=1,也就是獨立表空間文件,那么將無法完全恢復數據,本人也已經測試過,官方文檔也沒有提到啟用獨立表空間是否可以成功,在官方文檔中,是設置innodb_file_per_table=0。

參考資料如下:

http://www.percona.com/docs/wiki/innodb-data-recovery-tool:mysql-data-recovery:example_data_loss_scenario

4.1 切分頁

運行page_parser工具進行切分:

如果MySQL是5.0之前的版本,InnoDB采取的是REDUNDANT格式,運行以下命令:

./page_parser -4 -f /path/to/ibdata1

如果MySQL是5.0以后的版本,InnoDB采取的是COMPACT格式,運行以下命令:

./page_parser -5 -f /path/to/ibdata1

運行后,page_parser工具會創建一個pages-<TIMESTAMP>的目錄,其中TIMESTAMP是UNIX系統時間戳。在這個目錄下,為每個index ID,以頁的index ID創建一個子目錄。例如:

[root@localhost percona-data-recovery-tool]# ./page_parser -5 -f /data/mysql/ibdata1 
Opening file: /data/mysql/ibdata1:
2053            ID of device containing file
130915          inode number
33200           protection
1               number of hard links
500             user ID of owner
501             group ID of owner
0               device ID (if special file)
371195904               total size, in bytes
4096            blocksize for filesystem I/O
725000          number of blocks allocated
1394179630      time of last access
1394179669      time of last modification
1394179669      time of last status change
371195904       Size to process in bytes
104857600       Disk cache size in bytes
1.00% done. 2014-03-07 16:31:45 ETA(in 00:04 hours). Processing speed: 1237320 B/sec
2.00% done. 2014-03-07 16:28:27 ETA(in 00:01 hours). Processing speed: 3726376 B/sec
8.80% done. 2014-03-07 16:27:04 ETA(in 00:00 hours). Processing speed: 25231360 B/sec
9.80% done. 2014-03-07 16:28:22 ETA(in 00:01 hours). Processing speed: 3719168 B/sec
20.80% done. 2014-03-07 16:27:00 ETA(in 00:00 hours). Processing speed: 40819863 B/sec
28.25% done. 2014-03-07 16:27:35 ETA(in 00:00 hours). Processing speed: 6912218 B/sec
40.96% done. 2014-03-07 16:27:02 ETA(in 00:00 hours). Processing speed: 47167140 B/sec
51.43% done. 2014-03-07 16:27:03 ETA(in 00:00 hours). Processing speed: 38881628 B/sec
56.49% done. 2014-03-07 16:27:37 ETA(in 00:00 hours). Processing speed: 4698112 B/sec
76.23% done. 2014-03-07 16:27:05 ETA(in 00:00 hours). Processing speed: 73252864 B/sec
83.23% done. 2014-03-07 16:27:07 ETA(in 00:00 hours). Processing speed: 26001408 B/sec
84.74% done. 2014-03-07 16:28:00 ETA(in 00:00 hours). Processing speed: 1117388 B/sec
90.79% done. 2014-03-07 16:27:12 ETA(in 00:00 hours). Processing speed: 22452811 B/sec
99.00% done. 2014-03-07 16:27:12 ETA(in 00:00 hours). Processing speed: 30479427 B/sec
[root@localhost percona-data-recovery-tool]# 
[root@localhost percona-data-recovery-tool]# ll pages-1394180806/FIL_PAGE_INDEX/0-1
total 16
-rw-r--r-- 1 root root 16384 Mar  7 16:26 1-00000008.page

4.2 選擇需要的Index ID

一般來說,我們需要根據表的主鍵(PRIMARY index)進行恢復,主鍵中包含了所有的行。以下是一些可以實現的步驟:

如果數據庫仍處於運行狀態,並且表沒有被drop掉,那么可以啟動InnoDB Tablespace Monitor,輸出所有表和indexes,index IDs到MySQL server的錯誤日志文件。創建innodb_table_monitor表用於收集innodb存儲引擎表及其索引的存儲方式:

mysql> CREATE TABLE test.innodb_table_monitor (id int) ENGINE=InnoDB;
Query OK, 0 rows affected (0.31 sec)

mysql> 

如果innodb_table_monitor已經存在,drop表然后重新create表。等MySQL錯誤日志輸出后,可以drop掉這張表以停止打印輸出更多的監控。一個輸出的例子如下:

TABLE: name book/million_words, id 239, flags 1, columns 5, indexes 2, appr.rows 0
  COLUMNS: id: DATA_INT DATA_UNSIGNED DATA_BINARY_TYPE DATA_NOT_NULL len 4; word: DATA_VARMYSQL DATA_NOT_NULL len 150; DB_ROW_ID: DATA_SYS prtype 256 len 6; DB_TRX_ID: DATA_SYS prtype 257 len 6; DB_ROLL_PTR: DAT
A_SYS prtype 258 len 7;
  INDEX: name PRIMARY, id 374, fields 1/4, uniq 1, type 3
   root page 16419, appr.key vals 0, leaf pages 1, size pages 1
   FIELDS:  id DB_TRX_ID DB_ROLL_PTR word
  INDEX: name word, id 375, fields 1/2, uniq 1, type 2
   root page 16420, appr.key vals 0, leaf pages 1, size pages 1
   FIELDS:  word id

這里,我們恢復的是sakila庫下的customer表,從上面可以獲取其主鍵信息:

INDEX: name PRIMARY, id 374, fields 1/4, uniq 1, type 3

Index ID是0 374,因此我們需要恢復的InnoDB頁位於0-374子目錄下。

備注:參考文檔原文中之描述了以上這種獲取表的index ID的方法,本文在實際操作中,采取了更簡單的一種方式,即直接恢復page_parser生成的所有InnoDB頁。實踐證明這種方法也是可行的.

5. 生成表定義

步驟4中,我們已經找到了需要的數據,接下來需要找到表結構,創建表定義,將其編譯到constraints_parser中,然后使用這個工具從InnoDB頁中提取表中的行。

表定義包含了表中的列、列順序、數據類型。如果MySQL server仍處於運行且表未被drop掉,那么簡單實用SHOW CREATE TABLE就可以收集到這些信息。接下來將使用這些表結構信息來創建一個C結構體標識的表定義,然后編譯到constraints_parser工具。C結構體的定義存放在include/table_defs.h中。

最簡單的方式是create_defs.pl Perl 腳本,連接到MySQL server,讀取SHOW CREATE TABLE的結果,輸出生成的表定義到標准輸出。下面是個例子,其中直接將結果重定向到了include/table_defs.h中:

[root@localhost percona-data-recovery-tool]# ./create_defs.pl --host=127.0.0.1 --user=root --password=yayun --db=book --table=million_words  > include/table_defs.h

下面是生成的表定義:

#ifndef table_defs_h
#define table_defs_h

// Table definitions
table_def_t table_definitions[] = {
        {
                name: "million_words",
                {
                        { /* int(10) unsigned */
                                name: "id",
                                type: FT_UINT,
                                fixed_length: 4,

                                has_limits: FALSE,
                                limits: {
                                        can_be_null: FALSE,
                                        uint_min_val: 0,
                                        uint_max_val: 4294967295ULL
                                },

                                can_be_null: FALSE
                        },
                        { /*  */
                                name: "DB_TRX_ID",
                                type: FT_INTERNAL,
                                fixed_length: 6,

                                can_be_null: FALSE
                        },
                        { /*  */
                                name: "DB_ROLL_PTR",
                                type: FT_INTERNAL,
                                fixed_length: 7,

                                can_be_null: FALSE
                        },
                        { /* varchar(50) */
                                name: "word",
                                type: FT_CHAR,
                                min_length: 0,
                                max_length: 150,

                                has_limits: FALSE,
                                limits: {
                                        can_be_null: FALSE,
                                        char_min_len: 0,
                                        char_max_len: 150,
                                        char_ascii_only: TRUE
                                },

                                can_be_null: FALSE
                        },
                        { type: FT_NONE }
                }
        },
};

#endif

如果需要,可以根據需要編輯修改include/table_defs.h;然后根據include/table_defs.h,重新編譯constraints_parser工具:

[root@localhost percona-data-recovery-tool]# make
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -Wall -O3 -g -I include -I mysql-source/include -I mysql-source/innobase/include -c tables_dict.c -o lib/tables_dict.o
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -Wall -O3 -g -I include -I mysql-source/include -I mysql-source/innobase/include -c print_data.c -o lib/print_data.o 
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -Wall -O3 -g -I include -I mysql-source/include -I mysql-source/innobase/include -c check_data.c -o lib/check_data.o
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -Wall -O3 -g -I include -I mysql-source/include -I mysql-source/innobase/include -o constraints_parser constraints_parser.c lib/tables_dict.o lib/print_data.o lib/check_data.o lib/libut.a lib/libmystrings.a
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -Wall -O3 -g -I include -I mysql-source/include -I mysql-source/innobase/include -static -lrt -o page_parser page_parser.c lib/tables_dict.o lib/libut.a 
[root@localhost percona-data-recovery-tool]# 

6. 從頁中提取行記錄

6.1 合並頁到一個文件

前面已經提到,我們需要恢復的index ID 0 374,包含數據的頁位於pages-1394180806/FIL_PAGE_INDEX/0-374/ 目錄。

[root@localhost percona-data-recovery-tool]# cd pages-1394180806/FIL_PAGE_INDEX/0-374/
[root@localhost 0-374]# ll
total 36176
-rw-r--r-- 1 root root 16384 Mar  7 16:26 104-00000306.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 106-00000309.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 107-00000310.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 109-00000312.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 110-00000314.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 11-00001416.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 112-00000316.page
-rw-r--r-- 1 root root 16384 Mar  7 16:27 11732-00016419.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 13-00016419.page
-rw-r--r-- 1 root root 16384 Mar  7 16:26 16-00003437.page
................................
................................

輸入以下命令進行合並頁:

[root@localhost percona-data-recovery-tool]# find pages-1394180806/FIL_PAGE_INDEX/0-374/ -type f -name '*.page' | sort -n | xargs cat > pages-1394180806/FIL_PAGE_INDEX/0-374/customer_pages_concatenated
[root@localhost percona-data-recovery-tool]#

生成的結果文件:pages-1394180806/FIL_PAGE_INDEX/0-374/customer_pages_concatenated,將作為constraints_parser工具的輸入。

6.2 運行constraints_parser工具

下面到恢復數據最核心的步驟——運行constraints_parser工具以提取行記錄。和page_parser工具一樣,需要通過-5或-4參數指定InnoDB頁格式(COMPACT/REDUNDANT),-f指定輸入文件。

回到例子中,我們可以這樣運行constraints_parser工具

我們可以這樣運行constraints_parser工具(下面的命令是恢復一個單一的頁,也可以直接恢復經過6.1步驟合並所有頁之后的文件):

./constraints_parser -5 -f pages-1394180806/FIL_PAGE_INDEX/0-374/76-00003397.page

會輸出恢復數據相關語句:

LOAD DATA INFILE '/usr/local/percona-data-recovery-tool/dumps/default/million_words' REPLACE INTO TABLE `million_words` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'million_words\t' (id, word);

既然這樣,那么我們就創建dumps/default/文件夾

[root@localhost percona-data-recovery-tool]# pwd
/usr/local/percona-data-recovery-tool
[root@localhost percona-data-recovery-tool]# mkdir dumps/default -p
[root@localhost percona-data-recovery-tool]# 

恢復全部頁的數據到/dumps/default/million_words

[root@localhost percona-data-recovery-tool]# ./constraints_parser -5 -f pages-1394180806/FIL_PAGE_INDEX/0-374/customer_pages_concatenated >> dumps/default/million_words

輸出提示如下,省略了一些內容:

95.98% done
96.42% done
96.86% done
97.30% done
97.74% done
98.19% done
98.63% done
99.07% done
99.51% done
99.96% done
LOAD DATA INFILE '/usr/local/percona-data-recovery-tool/dumps/default/million_words' REPLACE INTO TABLE `million_words` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'million_words\t' (id, word);

7. 導入數據到數據庫中

mysql> use book
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> LOAD DATA INFILE '/usr/local/percona-data-recovery-tool/dumps/default/million_words' REPLACE INTO TABLE `million_words` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'million_words\t' (id, word);
Query OK, 1062600 rows affected (14.99 sec)
Records: 1031300  Deleted: 31300  Skipped: 0  Warnings: 0

mysql> select count(*) from million_words;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.20 sec)

mysql> 

可以看見數據已經恢復回來,我測試的MySQL版本如下:

mysql> select version();           
+-------------+
| version()   |
+-------------+
| 5.5.25a-log |
+-------------+
1 row in set (0.00 sec)

mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | OFF   |
+-----------------------+-------+
1 row in set (0.00 sec)

mysql> 

希望各位小伙伴永遠不需要用到此方法,備份才是王道啊!

參考資料如下:

http://www.percona.com/docs/wiki/innodb-data-recovery-tool:mysql-data-recovery:start

http://hidba.org/?p=852

 


免責聲明!

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



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