Recover InnoDB dictionary

为什么我们需要恢复innodb的字典信息?当我们drop 一个表时,发现误操作,这时又没有备份,那么想恢复数据是非常困难的。所以我们想恢复被删除的表时,首先就需要恢复表结构,目前已经有了undrop-for-innodb这个工具,相比之前的工具percona-data-recovery-tool-for-innodb有了更大的进步,这两个工具都是同一个作者开发的。这个工具我以前也写了相关的博客--Percona Data Recovery Tool 单表恢复,现在undrop-for-innodb这个工具更加完美,少了很多限制。但是对于drop table以后表结构的恢复还是需要使用percona-data-recovery-tool-for-innodb。没有看见undrop-for-innodb可以恢复表结构,这也是比较遗憾的地方吧。

1.TwinDB恢复工具包编译安装

安装工具包依赖的软件

yum install gcc bison flex bzr -y

2.下载软件包并编译安装

[root@mysql-server-01 ~]# bzr branch lp:undrop-for-innodb
You have not informed bzr of your Launchpad ID, and you must do this to
write to Launchpad or access private data.  See "bzr help launchpad-login".
Branched 18 revision(s).                                                                                                                                              
[root@mysql-server-01 ~]# 
[root@mysql-server-01 ~]# cd undrop-for-innodb/
[root@mysql-server-01 undrop-for-innodb]# make
cc -g -O3 -I./include -c stream_parser.c
cc -g -O3 -I./include  -pthread -lm stream_parser.o -o stream_parser
flex  sql_parser.l
bison  -o sql_parser.c sql_parser.y
sql_parser.y: conflicts: 6 shift/reduce
cc -g -O3 -I./include -c sql_parser.c 
cc -g -O3 -I./include -c c_parser.c
cc -g -O3 -I./include -c tables_dict.c
cc -g -O3 -I./include -c print_data.c
cc -g -O3 -I./include -c check_data.c
cc -g -O3 -I./include  sql_parser.o c_parser.o tables_dict.o print_data.o check_data.o -o c_parser -pthread -lm
cc -g -O3 -I./include -o innochecksum_changer innochecksum.c
[root@mysql-server-01 undrop-for-innodb]# 

如果没有抛出错误,证明就ok了,会生成编译好的工具c_parser。

一. 解析 ibdata1

InnoDB的字典信息存储在ibdata1。因此,我们需要使用stream_parser工具对它进行解析并获得存储在字典中的记录页。该工具在make以后就自动生成了。

[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:            1407057268 Sun Aug  3 17:14:28 2014
time of last modification:      1407057326 Sun Aug  3 17:15:26 2014
time of last status change:     1407057326 Sun Aug  3 17:15:26 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这个工具发现在ibdata1中,找到了存储页面类型(FIL_PAGE_INDEX或FIL_PAGE_TYPE_BLOB)由index_id的整理InnoDB的页面。

[root@mysql-server-01 pages-ibdata1]# pwd
/root/undrop-for-innodb/pages-ibdata1
[root@mysql-server-01 pages-ibdata1]# ll
total 8
drwxr-xr-x 2 root root 4096 Aug  3 17:15 FIL_PAGE_INDEX
drwxr-xr-x 2 root root 4096 Aug  3 17:15 FIL_PAGE_TYPE_BLOB
[root@mysql-server-01 pages-ibdata1]# 

SYS_TABLES

SYS_TABLES本身是一个表,该表用于规范innodb各种表定义和保存innodb中各种表的基本信息,结合SYS_COLUMNS,SYS_INDEXES和SYS_FOREIGN系统表定义了特定某个表的所有信息。

[root@mysql-server-01 undrop-for-innodb]# ll pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page 
-rw-r--r-- 1 root root 16384 Aug  3 17:15 pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page

SYS_INDEXES

SYS_INDEXES用于保存innodb中每个表定义的每个索引对象。

[root@mysql-server-01 undrop-for-innodb]# ll pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page 
-rw-r--r-- 1 root root 16384 Aug  3 17:15 pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page

SYS_COLUMNS

SYS_COLUMNS用于保存innodb引擎每个表定义的列,与SYS_TABLES相似。

[root@mysql-server-01 undrop-for-innodb]# ll pages-ibdata1/FIL_PAGE_INDEX/0000000000000002.page 
-rw-r--r-- 1 root root 16384 Aug  3 17:15 pages-ibdata1/FIL_PAGE_INDEX/0000000000000002.page

SYS_FIELDS

SYS_FIELDS用于保存innodb中每个索引的每个列对象。

[root@mysql-server-01 undrop-for-innodb]# ll pages-ibdata1/FIL_PAGE_INDEX/0000000000000004.page
-rw-r--r-- 1 root root 16384 Aug  3 17:15 pages-ibdata1/FIL_PAGE_INDEX/0000000000000004.page

从SYS_TABLES和SYS_INDEXES导出记录 

获取记录了需要使用c_parser工具。但首先我们要创建转存的数据目录 

[root@mysql-server-01 undrop-for-innodb]# mkdir -p dumps/default
[root@mysql-server-01 undrop-for-innodb]# 

InnoDB的字典总是在冗余格式,因此选择-4是必需的:

[root@mysql-server-01 undrop-for-innodb]# ./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
[root@mysql-server-01 undrop-for-innodb]# 

 现在找到我们关心的数据库,我之前有导入MySQL官方的示例数据库,sakila

[root@mysql-server-01 undrop-for-innodb]# grep sakila dumps/default/SYS_TABLES
000000000312    93000001320110  SYS_TABLES      "sakila/actor"  13      4       1       0       0       ""      0
000000000313    94000001350110  SYS_TABLES      "sakila/address"        14      8       1       0       0       ""      0
000000000314    95000001380110  SYS_TABLES      "sakila/category"       15      3       1       0       0       ""      0
000000000315    960000013A0110  SYS_TABLES      "sakila/city"   16      4       1       0       0       ""      0
000000000316    970000013D0110  SYS_TABLES      "sakila/country"        17      3       1       0       0       ""      0
000000000317    98000001400110  SYS_TABLES      "sakila/customer"       18      9       1       0       0       ""      0
000000000319    9A000001460110  SYS_TABLES      "sakila/film"   19      13      1       0       0       ""      0
00000000031B    9C0000014C0110  SYS_TABLES      "sakila/film\_actor"    20      3       1       0       0       ""      0
00000000031D    9E000001500110  SYS_TABLES      "sakila/film\_category" 21      3       1       0       0       ""      0
00000000031F    A0000001540110  SYS_TABLES      "sakila/inventory"      22      4       1       0       0       ""      0
000000000321    A2000001590110  SYS_TABLES      "sakila/language"       23      3       1       0       0       ""      0
000000000322    A30000015B0110  SYS_TABLES      "sakila/payment"        24      7       1       0       0       ""      0
000000000325    A6000001620110  SYS_TABLES      "sakila/rental" 25      7       1       0       0       ""      0
000000000328    A90000016A0110  SYS_TABLES      "sakila/staff"  26      11      1       0       0       ""      0
00000000032A    AB0000016F0110  SYS_TABLES      "sakila/store"  27      4       1       0       0       ""      0
[root@mysql-server-01 undrop-for-innodb]# 

可见该库下的所有表都看见了(用过sakila示例库的同学都知道^_^)。

dumps/default/SYS_TABLES使使用LOAD DATA INFILE命令生成的。具体的命令c_parsers打印到标准错误输出。把改SQL保存在了dumps/default/SYS_TABLES.sql

[root@mysql-server-01 undrop-for-innodb]# cat dumps/default/SYS_TABLES.sql 
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 '	' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'SYS_TABLES	' (`NAME`, `ID`, `N_COLS`, `TYPE`, `MIX_ID`, `MIX_LEN`, `CLUSTER_NAME`, `SPACE`);

用同样的方法导出SYS_INDEXES:

[root@mysql-server-01 undrop-for-innodb]# ./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 
[root@mysql-server-01 undrop-for-innodb]# 

查看一下结果,确保没有异常。

[root@mysql-server-01 undrop-for-innodb]# head -5 dumps/default/SYS_INDEXES 
-- Page id: 11, Format: REDUNDANT, Records list: Valid, Expected records: (43 43)
000000000300    810000012D0177  SYS_INDEXES     11      11      "ID\_IND"       1       3       0       302
000000000300    810000012D01A5  SYS_INDEXES     11      12      "FOR\_IND"      1       0       0       303
000000000300    810000012D01D3  SYS_INDEXES     11      13      "REF\_IND"      1       0       0       304
000000000300    810000012D026D  SYS_INDEXES     12      14      "ID\_IND"       2       3       0       305
[root@mysql-server-01 undrop-for-innodb]# head -5 dumps/default/SYS_INDEXES.sql 
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 '	' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'SYS_INDEXES	' (`TABLE_ID`, `ID`, `NAME`, `N_FIELDS`, `TYPE`, `SPACE`, `PAGE_NO`);
[root@mysql-server-01 undrop-for-innodb]# 

现在我们可以使用字典了,如果表在数据库中,那么会更加方便。
加载字典表到MySQL

通过grep可以从SYS_TABLES和SYS_INDEXES的主要用途是得到为了表名index_id。如果SYS_TABLES和SYS_INDEXES在MySQL中,那么操作将变得更简单。

root@localhost : (none) 18:10:31> create database yayun;
Query OK, 1 row affected (0.00 sec)

root@localhost : (none) 18:10:42> 

上面创建的库主要用来导入两张表而已。

[root@mysql-server-01 undrop-for-innodb]# mysql -uroot -p123456 -S /data/mysql/user_3306/mysql.sock yayun < dictionary/SYS_TABLES.sql 
[root@mysql-server-01 undrop-for-innodb]# mysql -uroot -p123456 -S /data/mysql/user_3306/mysql.sock yayun < dictionary/SYS_INDEXES.sql 
[root@mysql-server-01 undrop-for-innodb]# 

继续导入数据

[root@mysql-server-01 undrop-for-innodb]# mysql -uroot -p123456 -S /data/mysql/user_3306/mysql.sock yayun < dumps/default/SYS_TABLES.sql    
[root@mysql-server-01 undrop-for-innodb]# mysql -uroot -p123456 -S /data/mysql/user_3306/mysql.sock yayun < dumps/default/SYS_INDEXES.sql 
[root@mysql-server-01 undrop-for-innodb]# 

现在我们的InnoDB字典信息已经存在在MySQL中,我们可以像普通的表进行查询

root@localhost : (none) 20:56:44> use yayun
Database changed
root@localhost : yayun 20:56:48> SELECT * FROM SYS_TABLES WHERE NAME = 'sakila/actor';
+--------------+----+--------+------+--------+---------+--------------+-------+
| NAME         | ID | N_COLS | TYPE | MIX_ID | MIX_LEN | CLUSTER_NAME | SPACE |
+--------------+----+--------+------+--------+---------+--------------+-------+
| sakila/actor | 13 |      4 |    1 |      0 |       0 |              |     0 |
+--------------+----+--------+------+--------+---------+--------------+-------+
1 row in set (0.08 sec)

root@localhost : yayun 20:56:51> SELECT * FROM SYS_INDEXES WHERE TABLE_ID = 13; 
+----------+----+---------------------+----------+------+-------+---------+
| TABLE_ID | ID | NAME                | N_FIELDS | TYPE | SPACE | PAGE_NO |
+----------+----+---------------------+----------+------+-------+---------+
|       13 | 15 | PRIMARY             |        1 |    3 |     0 |     307 |
|       13 | 16 | idx_actor_last_name |        1 |    0 |     0 |     308 |
+----------+----+---------------------+----------+------+-------+---------+
2 rows in set (0.05 sec)

root@localhost : yayun 20:58:14> 

通过上述的查询,我们可以看到sakila.actor表有两个索引,PRIMARY和idx_actor_last_name,分别对应15,16,。如果我们不小心误操作,drop table actor,那么恢复数据就需要知道相应的id。后续文章将介绍drop table后如何恢复数据。

参考资料

https://twindb.com/how-to-recover-innodb-dictionary/

https://twindb.com/innodb-dictionary/

原文地址:https://www.cnblogs.com/gomysql/p/3888549.html