當DROP TABLE指令敲下的時候,你很爽,你有考慮過后果么?如果該表真的沒用,你DROP到無所謂,如果還有用的,這時你肯定嚇慘了吧,如果你有備份,那么恭喜你,逃過一劫,如果沒有備份呢?這時就該絕望了?NO! 如果你的表是innodb表,那么還有希望挽救,如果是myisam表,那么真的沒救了。前面文章介紹了 Recover InnoDB dictionary,這是恢復數據的前提。恢復innodb字典信息使用的是TwinDB recovery toolkit,我們恢復數據也是使用該工具。下面的案例是基於innodb_file_per_table=OFF的前提下,即使用共享表空間,所有的信息都保存在ibdata1中。使用獨立表空間DROP TABLE后數據恢復將在后面的文章介紹。
錯誤的操作--刪除表
用到的示例數據庫還是sakila,關於下載地址前面的文章有地址。將模擬把sakila庫中的actor表刪除后進行恢復。
root@localhost : sakila 21:34:11> SELECT * FROM actor LIMIT 10; +----------+------------+--------------+---------------------+ | actor_id | first_name | last_name | last_update | +----------+------------+--------------+---------------------+ | 1 | PENELOPE | GUINESS | 2006-02-15 04:34:33 | | 2 | NICK | WAHLBERG | 2006-02-15 04:34:33 | | 3 | ED | CHASE | 2006-02-15 04:34:33 | | 4 | JENNIFER | DAVIS | 2006-02-15 04:34:33 | | 5 | JOHNNY | LOLLOBRIGIDA | 2006-02-15 04:34:33 | | 6 | BETTE | NICHOLSON | 2006-02-15 04:34:33 | | 7 | GRACE | MOSTEL | 2006-02-15 04:34:33 | | 8 | MATTHEW | JOHANSSON | 2006-02-15 04:34:33 | | 9 | JOE | SWANK | 2006-02-15 04:34:33 | | 10 | CHRISTIAN | GABLE | 2006-02-15 04:34:33 | +----------+------------+--------------+---------------------+ 10 rows in set (0.01 sec) root@localhost : sakila 21:34:25>
root@localhost : sakila 21:34:25> CHECKSUM TABLE actor; +--------------+------------+ | Table | Checksum | +--------------+------------+ | sakila.actor | 2472295518 | +--------------+------------+ 1 row in set (0.07 sec) root@localhost : sakila 21:35:30> SET foreign_key_checks=OFF; Query OK, 0 rows affected (0.00 sec) root@localhost : sakila 21:35:46> DROP TABLE actor; Query OK, 0 rows affected (0.07 sec) root@localhost : sakila 21:35:57>
從ibdata1恢復數據
現在actor表已經刪除,但表中的信息仍然存與ibdata1中。該數據保持不變,直到InnoDB的重用空閑的頁。我們需要盡快停止mysqld進程。
對於恢復,我們將使用TwinDB恢復工具包。看看我前面的文章Recover InnoDB dictionary。
解析innodb表空間(ibdata1)
InnoDB將所有數據存儲在B +樹索引。 一個表有只有一個聚集索引,所有字段存儲在這里。 如果表有輔助索引,由index_id標識每個索引。
如果我們要恢復一個表,我們必須找到屬於特定index_id的所有頁面。
stream_parser讀取InnoDB表和排序按類型和每個index_id的InnoDB的頁面。
[root@mysql-server-01 undrop-for-innodb]# ./stream_parser -f /data/mysql/user_3306/data/ibdata1 Opening file: /data/mysql/user_3306/data/ibdata1 File information: ID of device containing file: 2055 inode number: 77760163 protection: 100660 (regular file) number of hard links: 1 user ID of owner: 498 group ID of owner: 500 device ID (if special file): 0 blocksize for filesystem I/O: 4096 number of blocks allocated: 53248 time of last access: 1407057329 Sun Aug 3 17:15:29 2014 time of last modification: 1407072967 Sun Aug 3 21:36:07 2014 time of last status change: 1407072967 Sun Aug 3 21:36:07 2014 total size, in bytes: 27262976 (26.000 MiB) Size to process: 27262976 (26.000 MiB) All workers finished in 0 sec [root@mysql-server-01 undrop-for-innodb]#
使用stream_parser將把數據從page保存到pages-ibdata1
[root@mysql-server-01 FIL_PAGE_INDEX]# pwd /root/undrop-for-innodb/pages-ibdata1/FIL_PAGE_INDEX [root@mysql-server-01 FIL_PAGE_INDEX]# ll total 6976 -rw-r--r-- 1 root root 32768 Aug 3 21:59 0000000000000001.page -rw-r--r-- 1 root root 49152 Aug 3 21:59 0000000000000002.page -rw-r--r-- 1 root root 49152 Aug 3 21:59 0000000000000003.page -rw-r--r-- 1 root root 49152 Aug 3 21:59 0000000000000004.page -rw-r--r-- 1 root root 32768 Aug 3 21:59 0000000000000005.page 。。。。。。。。。。。。。。。。。。。。。。。。。 -rw-r--r-- 1 root root 16384 Aug 3 21:59 0000000000000011.page -rw-r--r-- 1 root root 16384 Aug 3 21:59 0000000000000012.page -rw-r--r-- 1 root root 16384 Aug 3 21:59 0000000000000013.page -rw-r--r-- 1 root root 16384 Aug 3 21:59 0000000000000053.page -rw-r--r-- 1 root root 16384 Aug 3 21:59 0000000000000054.page -rw-r--r-- 1 root root 16384 Aug 3 21:59 0000000000000055.page -rw-r--r-- 1 root root 16384 Aug 3 21:59 18446744069414584320.page
現在InnoDB表空間的每個index_id被保存在一個單獨的文件。我們可以用c_parser工具從page提取記錄。但是,我們需要知道哪個index_id對應表Sakila/actor。這些信息,我們可以從字典中獲取:SYS_TABLES和SYS_INDEXES。
SYS_TABLES始終存儲在文件index_id為1的page,ibdata1/FIL_PAGE_INDEX/0000000000000001.page 這讓我們找到Sakila/actor表的標識符。如果MySQL有足夠的時間來刷新到磁盤的變化再加入D選項,意思是“尋找被刪除的記錄“,innodb字典信息永遠是冗余格式,所以我們需要指定選項-4。
[root@mysql-server-01 undrop-for-innodb]# ./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql | grep sakila/actor 000000000344 45000002B902C8 SYS_TABLES "sakila/actor" 13 4 1 0 0 "" 0 000000000344 45000002B902C8 SYS_TABLES "sakila/actor" 13 4 1 0 0 "" 0 SET FOREIGN_KEY_CHECKS=0; LOAD DATA LOCAL INFILE '/root/undrop-for-innodb/dumps/default/SYS_TABLES' REPLACE INTO TABLE `SYS_TABLES` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'SYS_TABLES\t' (`NAME`, `ID`, `N_COLS`, `TYPE`, `MIX_ID`, `MIX_LEN`, `CLUSTER_NAME`, `SPACE`); [root@mysql-server-01 undrop-for-innodb]#
注意表名之后的數13。這是表標識符。這和前面的文章不謀而合,Recover InnoDB dictionary
接下來的事情,需要做的是找到actor表的的主鍵ID。為此,我們將從SYS_INDEXES文件0000000000000003.page獲取記錄(該表將包含有關index_id和表標識符信息)。 SYS_INDEXES的結構需要通過-t選項解析。
[root@mysql-server-01 undrop-for-innodb]# ./c_parser -4Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql | grep 13 000000000344 45000002B90145 SYS_INDEXES 13 15 "PRIMARY" 1 3 0 4294967295 000000000344 45000002B901B7 SYS_INDEXES 13 16 "idx\_actor\_last\_name" 1 0 0 4294967295 000000000344 45000002B90145 SYS_INDEXES 13 15 "PRIMARY" 1 3 0 4294967295 000000000344 45000002B901B7 SYS_INDEXES 13 16 "idx\_actor\_last\_name" 1 0 0 4294967295 000000000344 45000002B90145 SYS_INDEXES 13 15 "PRIMARY" 1 3 0 4294967295 000000000344 45000002B901B7 SYS_INDEXES 13 16 "idx\_actor\_last\_name" 1 0 0 4294967295 SET FOREIGN_KEY_CHECKS=0; LOAD DATA LOCAL INFILE '/root/undrop-for-innodb/dumps/default/SYS_INDEXES' REPLACE INTO TABLE `SYS_INDEXES` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'SYS_INDEXES\t' (`TABLE_ID`, `ID`, `NAME`, `N_FIELDS`, `TYPE`, `SPACE`, `PAGE_NO`); [root@mysql-server-01 undrop-for-innodb]#
我們可以從輸出看到,PRIMARY index_id標示符是15。因此,我們的數據將從0000000000000015.page尋找。
[root@mysql-server-01 undrop-for-innodb]# ./c_parser -6f pages-ibdata1/FIL_PAGE_INDEX/0000000000000015.page -t sakila/actor.sql | head -10 -- Page id: 307, Format: COMPACT, Records list: Valid, Expected records: (200 200) 00000000032C AD000001750110 actor 1 "PENELOPE" "GUINESS" "2006-02-15 04:34:33" 00000000032C AD00000175011A actor 2 "NICK" "WAHLBERG" "2006-02-15 04:34:33" 00000000032C AD000001750124 actor 3 "ED" "CHASE" "2006-02-15 04:34:33" 00000000032C AD00000175012E actor 4 "JENNIFER" "DAVIS" "2006-02-15 04:34:33" 00000000032C AD000001750138 actor 5 "JOHNNY" "LOLLOBRIGIDA" "2006-02-15 04:34:33" 00000000032C AD000001750142 actor 6 "BETTE" "NICHOLSON" "2006-02-15 04:34:33" 00000000032C AD00000175014C actor 7 "GRACE" "MOSTEL" "2006-02-15 04:34:33" 00000000032C AD000001750156 actor 8 "MATTHEW" "JOHANSSON" "2006-02-15 04:34:33" 00000000032C AD000001750160 actor 9 "JOE" "SWANK" "2006-02-15 04:34:33" [root@mysql-server-01 undrop-for-innodb]#
看見上面的輸出,是不是覺得希望來了?哈哈
上面的結果正是我們想要的,我們現在把數據存貯在文件中,然后倒入,創建dump/default目錄存儲數據。
[root@mysql-server-01 undrop-for-innodb]# mkdir -p dumps/default [root@mysql-server-01 undrop-for-innodb]#
[root@mysql-server-01 undrop-for-innodb]# ./c_parser -6f pages-ibdata1/FIL_PAGE_INDEX/0000000000000015.page -t sakila/actor.sql > dumps/default/actor 2> dumps/default/actor_load.sql [root@mysql-server-01 undrop-for-innodb]#
我們看看一個文件,其實是命令加載表而已
[root@mysql-server-01 undrop-for-innodb]# cat dumps/default/actor_load.sql SET FOREIGN_KEY_CHECKS=0; LOAD DATA LOCAL INFILE '/root/undrop-for-innodb/dumps/default/actor' REPLACE INTO TABLE `actor` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'actor\t' (`actor_id`, `first_name`, `last_name`, `last_update`); [root@mysql-server-01 undrop-for-innodb]#
將數據load回數據庫中
現在將數據恢復到數據庫中。但是,在導入數據以前,我們需要創建表actor(前提我們要有表結構備份,如果沒有只有使用另外的工具找到表結構Percona Data Recovery Tool)看來還是需要兩個工具結合使用啊。
root@localhost : sakila 23:03:50> source sakila/actor.sql
root@localhost : sakila 23:03:50> show create table actor\G *************************** 1. row *************************** Table: actor Create Table: 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 row in set (0.00 sec) root@localhost : sakila 23:04:36>
現在我們導入數據,恢復actor表
[root@mysql-server-01 undrop-for-innodb]# mysql -uroot -p123456 -S /data/mysql/user_3306/mysql.sock --local-infile Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 18 Server version: 5.5.37-log MySQL Community Server (GPL) Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. root@localhost : (none) 23:17:16> use sakila Database changed root@localhost : sakila 23:17:19> source dumps/default/actor_load.sql; Query OK, 0 rows affected (0.00 sec) Query OK, 600 rows affected (0.08 sec) Records: 400 Deleted: 200 Skipped: 0 Warnings: 0 root@localhost : sakila 23:17:22>
檢查恢復的數據
root@localhost : sakila 23:19:00> SELECT COUNT(*) FROM actor; +----------+ | COUNT(*) | +----------+ | 200 | +----------+ 1 row in set (0.00 sec) root@localhost : sakila 23:19:34> SELECT * FROM actor LIMIT 10; +----------+------------+--------------+---------------------+ | actor_id | first_name | last_name | last_update | +----------+------------+--------------+---------------------+ | 1 | PENELOPE | GUINESS | 2006-02-15 04:34:33 | | 2 | NICK | WAHLBERG | 2006-02-15 04:34:33 | | 3 | ED | CHASE | 2006-02-15 04:34:33 | | 4 | JENNIFER | DAVIS | 2006-02-15 04:34:33 | | 5 | JOHNNY | LOLLOBRIGIDA | 2006-02-15 04:34:33 | | 6 | BETTE | NICHOLSON | 2006-02-15 04:34:33 | | 7 | GRACE | MOSTEL | 2006-02-15 04:34:33 | | 8 | MATTHEW | JOHANSSON | 2006-02-15 04:34:33 | | 9 | JOE | SWANK | 2006-02-15 04:34:33 | | 10 | CHRISTIAN | GABLE | 2006-02-15 04:34:33 | +----------+------------+--------------+---------------------+ 10 rows in set (0.00 sec) root@localhost : sakila 23:19:37> CHECKSUM TABLE actor; +--------------+------------+ | Table | Checksum | +--------------+------------+ | sakila.actor | 2472295518 | +--------------+------------+ 1 row in set (0.00 sec) root@localhost : sakila 23:19:40>
可以發現和drop table之前完全一致。到這里數據就恢復完成啦。希望小伙伴們永遠不要使用到改工具。
參考資料
https://twindb.com/recover-innodb-table-after-drop-table-innodb/