如果MySQL的 InnoDB 文件的损坏,该如何手动恢复?

时间:2022-07-28
本文章向大家介绍如果MySQL的 InnoDB 文件的损坏,该如何手动恢复?,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

我们都知道MySQL 的复制技术,通过主从同步可以实现读写分离,热备份,让服务器更加高可用。MySQL 的复制主要是通过 Binlog 来完成的,Binlog 记录了数据库更新的事件,从库 I/O 线程会向主库发送 Binlog 更新的请求,同时主库二进制转储线程会发送 Binlog 给从库作为中继日志进行保存,然后从库会通过中继日志重放,完成数据库的同步更新

昨天整理一篇一篇在没有开启使用 Binlog 的情况下,尽可能地找回数据。数据库没有备份,没有使用Binlog的情况下,如何恢复数据?

下面InnoDB 文件损坏时的人工操作过程,下面用一个例子来模拟下

生成 InnoDB 数据表

为了简便,我们创建一个数据表 t1,只有 id 一个字段,类型为 int。使用命令create table t1(id int);即可

然后创建一个存储过程帮我们生成一些数据

BEGIN
-- 当前数据行
DECLARE i INT DEFAULT 0;
-- 最大数据行数
DECLARE max_num INT DEFAULT 100;
-- 关闭自动提交
SET autocommit=0;
REPEAT
SET i=i+1;
-- 向t1表中插入数据
INSERT INTO t1(id) VALUES(i);
UNTIL i = max_num
END REPEAT;
-- 提交事务
COMMIT;
END

然后我们运行call insert_t1(),这个存储过程帮我们插入了 100 条数据,这样我们就有了 t1.ibd 这个文件。

模拟损坏.ibd 文件

实际工作中我们可能会遇到各种各样的情况,比如.ibd 文件损坏等,如果遇到了数据文件的损坏,MySQL 是无法正常读取的。在模拟损坏.ibd 文件之前,我们需要先关闭掉 MySQL 服务,然后用编辑器打开 t1.ibd,类似下图所示:

文件是有二进制编码的,看不懂没有关系,我们只需要破坏其中的一些内容即可,比如我在 t1.ibd 文件中删除了 2 行内容(文件大部分内容为 0,我们在文件中间部分找到一些非 0 的取值,然后删除其中的两行:4284 行与 4285 行,原 ibd 文件和损坏后的 ibd 文件见

地址:https://gitee.com/iByteCoding/JavaBang

其中 t1.ibd 为创建的原始数据文件,t1- 损坏.ibd 为损坏后的数据文件,你需要自己创建 t1 数据表,然后将 t1- 损坏.ibd 拷贝到本地,并改名为 t1.ibd)。

然后我们保存文件,这时.ibd 文件发生了损坏,如果我们没有打开innodb_force_recovery,那么数据文件无法正常读取。为了能读取到数据表中的数据,我们需要修改 MySQL 的配置文件,找到[mysqld]的位置,然后再下面增加一行innodb_force_recovery=1。

备份数据表

当我们设置innodb_force_recovery参数为 1 的时候,可以读取到数据表 t1 中的数据,但是数据不全。我们使用SELECT * FROM t1 LIMIT 10;读取当前前 10 条数据。

但是如果我们想要完整的数据,使用SELECT * FROM t1 LIMIT 100;就会发生如下错误。

这是因为读取的部分包含了已损坏的数据页,我们可以采用二分查找判断数据页损坏的位置。这里我们通过实验,可以得出只有最后一个记录行收到了损坏,而前 99 条记录都可以正确读出(具体实验过程省略)。

这样我们就能判断出来有效的数据行的位置,从而将它们备份出来。首先我们创建一个相同的表结构 t2,存储引擎设置为 MyISAM。我刚才讲过这里使用 MyISAM 存储引擎是因为在innodb_force_recovery=1的情况下,无法对 innodb 数据表进行写数据。使用命令CREATE TABLE t2(id int) ENGINE=MyISAM;。

然后我们将数据表 t1 中的前 99 行数据复制给 t2 数据表,使用:

INSERT INTO t2 SELECT * FROM t1 LIMIT 99;

我们刚才讲过在分析 t1 数据表的时候无法使用 WHERE 以及 ORDER BY 等子句,这里我们可以实验一下,如果想要查询 id<10 的数据行都有哪些,那么会发生如下错误。原因是损坏的数据页无法进行条件判断。

删除旧表,改名新表

刚才我们已经恢复了大部分的数据。虽然还有一行记录没有恢复,但是能找到绝大部分的数据也是好的。然后我们就需要把之前旧的数据表删除掉,使用DROP TABLE t1;。

更新表名,将数据表名称由 t2 改成 t1,使用RENAME TABLE t2 to t1;。

将新的数据表 t1 存储引擎改成 InnoDB,不过直接修改的话,会报如下错误:

关闭innodb_force_recovery,并重启数据库

因为上面报错,所以我们需要将 MySQL 配置文件中的innodb_force_recovery=1删除掉,然后重启数据库。最后将 t1 的存储引擎改成 InnoDB 即可,使用ALTER TABLE t1 engine = InnoDB;。

总结

我们人工恢复了损坏的 ibd 文件中的数据,虽然没有 100% 找回,但是相比于束手无措来说,已经是不幸中的万幸,至少我们还可以把正确的数据页中的记录成功备份出来,尽可能恢复原有的数据表。在这个过程中相信你应该对 ibd 文件,以及 InnoDB 自身的强制恢复(Force Recovery)机制有更深的了解

数据表损坏,以及人为的误删除都不是我们想要看到的情况,但是我们不能指望运气,或者说我们不能祈祷这些事情不会发生。在遇到这些情况的时候,应该通过机制尽量保证数据库的安全稳定运行。这个过程最主要的就是应该及时备份,并且开启二进制日志,这样当有误操作的时候就可以通过数据库备份以及 Binlog 日志来完成数据恢复。同时采用延迟备份的策略也可以尽量抵御误操作。总之,及时备份是非常有必要的措施,同时我们还需要定时验证备份文件的有效性,保证备份文件可以正常使用

如果你遇到了数据库 ibd 文件损坏的情况,并且没有采用任何的备份策略,可以尝试使用 InnoDB 的强制恢复机制,启动 MySQL 并且将损坏的数据表转储到 MyISAM 数据表中,尽可能恢复已有的数据。总之机制比人为更靠谱,我们要为长期的运营做好充足的准备。一旦发生了误操作这种紧急情况,不要慌张,及时采取对应的措施才是最重要的