记一次MySQL删库的恢复
背景
最近正在做语音相关的工作,所有的语料数据都放在一个mysql数据库里面,同事一次误操作导致整个数据库被drop,里面包括了全部的文本语料和词库。 数据库放在一台linux机器上,文件系统是XFS。
从磁盘恢复数据
说实话,之前没有相关经验,只有先google一下看看有没有现成可以参考的解决方案。在stackexchange上看到有人提到TwinDB data recovery toolkit可以恢复,先需要停掉mysql,并停止一切写入,赶紧systemctl stop mysql,umount掉mysql database所在的disk。
MySQL 5.5之前是所有数据都放在ibdata1文件里面的,因此被删除的数据还在ibdata里面,而5.6之后是放在单独的目录里面,drop database/table是直接删除文件的。我们用的是8.0,不能直接扫描ibdata1,需要扫描整个磁盘,同时也意味着其他进程的写入也很可能导致数据被覆盖掉。不过既然是删除了文件,可以先试试能不能从xfs里面恢复删除的文件,找到一个工具,扫描了一遍磁盘,没能够找到对应的ibd文件。
TwinDB data recovery toolkit使用
先下载到本地编译
1 2 3git clone https://github.com/twindb/undrop-for-innodb cd undrop-for-innodb make使用stream_parser扫描整个磁盘
1./stream_parser /dev/sdc1会在当前目录下生成一个文件夹pages-XXX,XXX为扫描的文件名,我这里生成的是pages-sdc1,里面包含了FIL_PAGE_INDEX和FIL_PAGE_BLOB两个目录。
使用c_parser得到表corpora的ID,0000000000000001.page是存放SYS_TABLES的page文件
1 2 3./c_parser -4f pages-sdc1/FIL_PAGE_INDEX/0000000000000001.page -t dictionary/SYS_TABLES.sql | grep corpora 000000000504 85000001320110 SYS_TABLES "corpora" 54 4 1 0 0 "" 1因为InnoDB的数据是放在primary index上的,通过获取primary index的ID,就能够得到存放数据的page文件。0000000000000003.page是存放SYS_INDEXES的page文件
1 2 3./c_parser -4f pages-sdc1/FIL_PAGE_INDEX/0000000000000003.page -t dictionary/SYS_INDEXES.sql | grep 54 000000000504 85000001320178 SYS_INDEXES 54 112 "PRIMARY" 1 3 1 3找到了corpora的数据是在0000000000000112.page文件里面的 在恢复数据之前需要先确定表结构,还好我们知道,创建一个包含corpora DDL的sql文件放到corpora.sql
恢复数据
1 2 3mkdir -p dumps/default ./c_parser -6f pages-sdc1/FIL_PAGE_INDEX/0000000000000112.page \ -t corpora.sql > dumps/default/corpora 2> dumps/default/corpora.sql运行完成后,看看是否恢复成功
1head -100 dumps/default/corpora很遗憾,数据不全,而且还有很多错误数据,看来已经被覆盖掉了不少page。尝试恢复另外两张表也没什么收获。难道只能从删库到跑路了?
从backup恢复
之前有做一个定时backup的脚本,每天半夜进行backup,赶紧到backup的机器上看看,结果发现最近一次的backup是3个月前的了。执行
| |
是有对应的命令的,查了一下,有人说crontab命令后面必须有一行空行才能正确执行,还不确定是否是这个导致,顺手改掉,后面再定时检查。
从solr恢复
还好文字语料在solr中是有cache的,用于加速查询,赶紧连接到solr中,dump到文件中
| |
有了csv文件,写一个python脚本直接导入即可。
从内存中恢复
但是还有词表没恢复,这张表是没有solr缓存的,怎么办? 还好,我们有一个在线分词查询程序,会将词表cache到内存中,这是一个python写的http server,分词是使用c++实现的DLL,运行在windows上。
词表保存在一个hash table里面,结构如下:
| |
从代码可以看出来,用的是Seperated chaining方式存放的,如下图:

怎么从内存中还原词表呢?有两种方法:
- ReadProcessMemory():直接读取进程的内存空间
- MiniDump文件:保存为Minidump文件,然后读取这个文件
我选择第二个,使用Process Explorer保存一个dump文件。这里需要注意,必须保存full dump,否则Heap不会保存,后续无法获取heap中的数据。
minidump文件格式
不像其他文件格式,MS对minidump文件的格式描述还是很清楚的,在这里可以找到。有人已经给我们写好了python解析minidump的库,我们只需要找到全局变量数组bins,然后遍历这个数组找出所有的Word即可。
定位变量地址
在windows中,一个进程运行时是有自己独立进程空间的,这个进程依赖的DLL都会作为module被加载到这个进程的地址空间中来,我们在minidump中可以找到这个DLL所在的base address,然后在这个DLL中定位到这个变量所在offset,两者相加即可得到这个变量在整个进程空间中的地址。
minidump中包含了所有module的相关信息,而变量的偏移地址,就需要借助一些其他方法来获取了。如果有DLL对应的PDB文件,则比较容易,但是这个DLL的PDB文件也没有保存下来,只能想其他办法。
使用IDA Pro,打开这个DLL,通过exported function,很容易定位到这个操作这个变量的函数,然后在这个函数中找到这个全局变量的位置,然后用这个地址减去IDA加载DLL时的base address,就得到了这个变量的offset。
有了offset,接下来就是遍历hash table了。这里我使用了python的struct库来进行数据的数据的读取。
读取minidump
minidump这个库给我们提供了方便进行内存位置读取的方法,简化后的代码如下:
| |
总结
- 出现误删,一定要马上停止写入,umount磁盘,如果有可能,尽量保留磁盘镜像
- 数据库的权限如果没有设置好,会导致很容易就能删库跑路
- 数据库的备份没有起作用,一定要定时检查备份脚本是否正常工作
- 后面有时间对InnoDB的结构进行一下分析