作者在维护公司某小型系统数据库时,把本地测试环境和生产环境搞混了,使用drop database命令删除了生产系统中的某个库(再次提醒大家务必做好备份……,不要同时打开生产环境和本地环境的数据库)。执行了drop database命令了,立即发通知并停止Web系统,防止数据被覆盖。发愣几分钟后冷静下来开始思考解决方案。
查看备份任务没有对该库进行备份;查看mysql配置,发现没有开始log_bin,不能使用log_bin恢复数据。
使用SHOW VARIABLES LIKE '%datadir%'命令查看到数据文件存放在/var/lib/mysql下。
访问/var/lib/mysql发现数据目录下有一个ibdata1文件,说明没有开启innodb_file_per_table on功能。并立马对ibdata1文件进行了备份,后续恢复操作均在测试机上进行,以免再次对生产系统造成损坏。
在github上使用undrop innodb关键词发现undrop-for-innodb(https://github.com/twindb/undrop-for-innodb)工具,视乎看到了希望。
下载并编译undrop-for-innodb(需要make, gcc, flex ,bison)。安装完成后在目录下生成了stream_parser、c_parser等多个执行文件。将前面备份的ibdata1复制到~/mysql_restore/undrop-for-innodb目录下,作好恢复准备。整个恢复过程共分为4步。
cd ~/
mkdir mysql_restore
git clone https://github.com/twindb/undrop-for-innodb
cd undrop-for-innodb
make
使用如下命令对ibdata1进行扫描,扫描完成后会在当前目录下生成pages-ibdata1目录,目录中分别有FIL_PAGE_INDEX、FIL_PAGE_TYPE_BLOB目录。我们重点关注FIL_PAGE_INDEX,该目录中存放了所有从ibdata1中扫描出来的页索引文件。
./stream_parser -f ibdata1
页索引文件名由一串数字.page组成,文件名中的数字是后续操作的关键。
一般情况下表结构是存储在 frm 文件中,drop table 会删除 frm 文件,还好我们可以从 innodb 系统表里读取一些信息恢复表结构。innodb 系统表有SYS_COLUMNS 、SYS_FIELDS、 SYS_INDEXES 、SYS_TABLES
这几个表记录了所有库表的元数据,对于恢复工作非常重要。
根据MySQL的源码,这SYS_COLUMNS 、SYS_FIELDS、 SYS_INDEXES 、SYS_TABLES这4个表的数据分别位于页文件的最前面4个页中,使用下面的4条语句从这4个页中读出数据:
mkdir dumps
./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
页文件可能不是从1开始的,要根据自己的情况修改
dumps目录中的4个sql文件就是从ibdata1文件中恢复的所有表结构信息。使用如下命令将恢复出来的表结构恢复到mysql中的data_recovered数据库下(-p后放自己的root密码)。
# cat dumps/default/*.sql | mysql -uroot -p*** eim3
命令执行完后即可在mysql中看到表已经恢复出来了,但是表中没有数据。将data_recovered中的每一个表的结构分别导出为sql文件并上传到undrop-for-innodb所在的目录中,比如使用sqlyog:
首先找到想恢复的表对应的table_id和index_id,执行如下命令。
./c_parser -5Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql |grep 'eim3'
可以得到eim3库下每个表的table_id,例如下图的000000B31AB5,这一行第5列的158,就说明第4列的表名对应的table_id为158,记住这个数字。
使用如下命令可以获得每一个table_id对应的index_id:
./c_parser -5Df pages-ibdata1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql
如下图中的第4列就是table_id,第5列就是index_id。
将前面的table_id和index_id复制出来,在excel中使用table_id进行匹配,即可得出每个表的index_id。比如下图中eim3.users_bonus对应的table_id为158,158对应的index_id为438,则说明eim3.users_bonus这个表中的数据存放在0000000000000438.page这个页文件下。
./c_parser -5Uf pages-ibdata1/FIL_PAGE_INDEX/0000000000000438.page -t obj_users_bonus.sql > dumps/default/users_bonus 2> dumps/default/users_bonus.sql
该命令会在dumps/default/下同时生成users_bonus 、users_bonus.sql两个文件,其中users_bonus 文件中存放了表中的数据,users_bonus.sql文件中存放了导入数据的命令。我们使用如下命令即可将数据恢复至表中。
cat users_bonus.sql | mysql -u*** -proot eim3
或者使用Excel直接组合生成所有表的扫描指令和恢复指令,然后在主机中批量执行。
至此,所有数据全部恢复,再发通告上线系统。