分享

MLOG_CHECKPOINT缺失下紧急数据恢复

 数据和云 2021-08-18
问题描述


生产环境MySQL数据库,架构是一主一从,可以双机切换,MySQL版本是5.7.24-enterprise-commercial-advanced-log。当时是存储MySQL的机房突然断电。主从机器重启后,MySQL都无法启动。两台机器都是同样错误,错误日志显示:

2020-10-27T00:58:41.032944Z 0 [Note] InnoDB: Log scan progressed past the checkpoint lsn 201710672038142020-10-27T00:58:41.136783Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201710724464642020-10-27T00:58:41.222121Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201710776893442020-10-27T00:58:41.312905Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201710829322242020-10-27T00:58:41.401125Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201710881751042020-10-27T00:58:41.493480Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201710934179842020-10-27T00:58:41.579078Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201710986608642020-10-27T00:58:41.608482Z 0 [Note] InnoDB: Doing recovery: scanned up to log sequence number 201711001728002020-10-27T00:58:41.608573Z 0 [ERROR] InnoDB: Ignoring the Redo log due to missing MLOG_CHECKPOINT between the checkpoint 20171067203814 and the end 20171100172800.2020-10-27T00:58:41.608587Z 0 [ERROR] InnoDB: Plugin initialization aborted with error Generic error2020-10-27T00:58:41.808775Z 0 [ERROR] Plugin 'InnoDBinit function returned error.2020-10-27T00:58:41.808802Z 0 [ERROR] Plugin 'InnoDBregistration as a STORAGE ENGINE failed.2020-10-27T00:58:41.808817Z 0 [ERROR] Failed to initialize builtin plugins.2020-10-27T00:58:41.808823Z 0 [ERROR] Aborting

【问题分析】


MySQL源码:

/* Look for MLOG_CHECKPOINT. /recv_group_scan_log_recs(group, &contiguous_lsn, false);/ The first scan should not have stored or applied any records. */ut_ad(recv_sys->n_addrs == 0);ut_ad(!recv_sys->found_corrupt_fs);

if (recv_sys->found_corrupt_log && !srv_force_recovery) {
log_mutex_exit();
return(DB_ERROR);
}

if (recv_sys->mlog_checkpoint_lsn == 0) {
if (!srv_read_only_mode
   && group->scanned_lsn != checkpoint_lsn) {
ib::error() << "Ignoring the redo log due to missing"
" MLOG_CHECKPOINT between the checkpoint "
<< checkpoint_lsn << " and the end "
<< group->scanned_lsn << ".";
if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) {
log_mutex_exit();
return(DB_ERROR);
}
}

在MySQL 5.7版本以前,InnoDB恢复的时候需要依赖数据字典,因为InnoDB根本不知道某个具体的space对应的ibd文件是哪个,这些信息都是数据字典维护的。而且在恢复前,需要把所有的表空间全部打开,如果库中有数以万计的表,把所有表打开一遍,整个过程就会很慢。那么MySQL 5.7在这上面做了哪些改进呢?

其实很简单,针对上面的问题,InnoDB在Redo log中增加了两种Redo log的类型来解决。MLOG_FILE_NAME用于记录在checkpoint之后所有被修改过的信息(space, filepath);MLOG_CHECKPOINT用于标志MLOG_FILE_NAME的结束。

上面两种Redo log类型的添加,完美解决了前面遗留的问题,Redo log中保存了后续需要恢复的space和filepath对。因此在恢复的时候,只需要从checkpoint的位置往后扫描到MLOG_CHECKPOINT的位置,这样就能获取到需要恢复的space和filepath,在恢复过程中,只需要打开这些ibd文件即可,当然由于space和filepath的对应关系通过Redo存了下来,恢复的时候也不再依赖数据字典。

这里需要强调的一点就是MLOG_CHECKPOINT在每个checkpoint点中最多只存在一次,如果出现多次MLOG_CHECKPOINT类型的日志,则说明Redo已经损坏,InnoDB会报错。最多存在一次,那么会不会有不存在的情况呢?

答案是肯定的,在每次checkpoint过后,如果没有发生数据更新,那么MLOG_CHECKPOINT就不会被记录。所以只要简单查找下Redo log最新一个checkpoint后的MLOG_CHECKPOINT是否存在,就能判定上次MySQL是否正常关机。5.7版本的MySQL在InnoDB进行恢复的时候,也正是这样做的。MySQL 5.7在进行恢复的时候,一般情况下需要进行最多3次的Redo log扫描:

第一次Redo log的扫描,主要是查找MLOG_CHECKPOINT,不进行Redo log的解析,如果没有找到MLOG_CHECKPOINT,则说明InnoDB不需要进行recovery,后面的两次扫描可以省略,如果找到了MLOG_CHECKPOINT,则获取MLOG_FILE_NAME到指定列表,后续只需打开该链表中的表空间即可。

MySQL会在Redo log写一个一字节的最新的MLOG_CHECKPOINT 标记,用来标记在此之前的Redo都已checkpoint完成。因为异常断电,已找不到这个标记,整个Redo日志文件都会被忽略,innodb引擎启动失败。若是有备份的话,可以用备份恢复,再通过binlog追上最新数据。若是没有备份的话,通过救援模式启动数据库,导出数据,再导入恢复。


【解决过程】


1.查找备份文件

发现备份脚本在很久之前就没工作了,备份文件不是最新的。无法使用备份恢复。

2.删除Redo文件后启动

主库备份了Redo文件后,删除Redo文件,启动,失败。报错:

[ERROR] InnoDB: Page [page id: space=0, page number=7] log sequence number 20170595558339 is in the future! Current system log sequence number 10136858555422.[ERROR] InnoDB: Your database may be corrupt or you may have copied the InnoDB tablespace but not the InnoDB log files. Please refer to http://dev.mysql.com/doc/refman/5.7/en/forcing-innodb-recovery.html for information about forcing recovery.

LSN(log sequence number)日志序列号,占用8字节,LSN主要用于发生crash时对数据进行recovery,LSN是一个一直递增的整型数字,表示事务写入到日志的字节总量。

LSN不仅只存在于重做日志中,在每个数据页头部也会有对应的LSN号,该LSN记录当前页最后一次修改的LSN号,用于在recovery时对比重做日志LSN号决定是否对该页进行恢复数据。

上面的报错显示,数据在磁盘的page包含了较新的LSN,而此时系统system表空间头的LSN却是旧LSN。

3.设置innodb_force_recovery的参数值

先是设置innodb_force_recovery=3,启动失败,后设置为4,设置为5,设置为6,最终启动成功。

innodb_force_recovery的参数说明:

1(SRV_FORCE_IGNORE_CORRUPT):忽略检查到的corrupt页。
2(SRV_FORCE_NO_BACKGROUND):阻止主线程的运行,如主线程需要执行full purge操作,会导致crash。
3(SRV_FORCE_NO_TRX_UNDO):不执行事务回滚操作。
4(SRV_FORCE_NO_IBUF_MERGE):不执行插入缓冲的合并操作。
5(SRV_FORCE_NO_UNDO_LOG_SCAN):不查看重做日志,InnoDB存储引擎会将未提交的事务视为已提交。
6(SRV_FORCE_NO_LOG_REDO):不执行前滚的操作。

这时候,主库是只读状态,可以导出数据。

4.导出

假如数据量不大的情况下,可以直接全库导出。

因为当前数据目录大容量占有1.7T,恢复业务的紧迫性和磁盘空间的不足,跟研发业务部门沟通,打算恢复基本的数据,几个超百G的大表只恢复近期数据。

1)导出系统库MySQL库(里面含有原实例账号数据)

mysqldump -uroot -p --databases mysql >mysql.sql

2)导出所有业务库表结构

mysql -uroot -p -e’show databases;’|grep -E -v “Database|mysql|sys|information_schema|performance_schema” | xargs mysqldump -uroot -p -d --triggers --routines --events --skip-lock-tables --set-gtid-purged=off --single-transaction --databases >alldb_desc.sql

3)导出所有业务库数据

忽略大表的数据没导出(根据实际情况,假如多个库的,分别导出,多个大表的,忽略多个),加快恢复速度。

mysqldump -uroot -p --set-gtid-purged=off -t 库名 --ignore-table=库名.忽略表名1 --ignore-table=库名.忽略表名2>库名.sql

4)导出大表部分数据

只保留近期数据(例如一个月的),过滤条件导出(根据实际情况,如果多个大表,多次导出)

mysqldump -uroot -proot --databases 库名 --tables 大表名 --where='ENTRY_DATE>=str_to_date('2020-10-01 00:00:00’,’%Y-%m-%d %H:%i:%s’)’ > 库名_大表名.sql

如果数据量大,导出时间长,可以考虑后台方式执行nohup 命令 &>>nohup.log

5.初始化实例

1)在从库机器上删除已存在数据和日志,初始化MySQL实例

mysqld --initialize --user=mysql

(备注:若是MySQL 5.6版本,初始化实例命令是mysql_install_db --user=mysql --defaults-file=/etc/my.cnf)

2)获取临时密码

cat mysqld.log|grep passwordmysql -uroot -p临时密码

(备注:若是MySQL 5.6版本,初始化密码为空)

3)修改root密码

alter user 'root’@'localhost’ identified by 'Root_123’;

6.导入

1)修改参数,加快导入

为了加快导入速度,建议关闭双1,甚至可以暂时关闭binlog。

修改前查参数值:

show GLOBAL variables like 'foreign_key_checks’;show GLOBAL variables like 'unique_checks’;show GLOBAL variables like 'innodb_flush_log_at_trx_commit’;show GLOBAL variables like 'sync_binlog’;

执行如下加速操作:

SET GLOBAL foreign_key_checks=0;SET GLOBAL unique_checks=0;SET GLOBAL innodb_flush_log_at_trx_commit=0;SET GLOBAL sync_binlog=0;

2)数据导入

在主库导出的备份文件,传输到从库机器上,依次导入:

mysql -uroot -p <mysql.sql

导入MySQL库后,记得执行flush privileges,重新加载权限表到内存:

mysql -uroot -p < alldb_desc.sqlmysql -uroot -p <库名.sqlmysql -uroot -p 库名 <库名_大表名.sql

如果数据量大,导入出时间长,可以考虑后台方式执行nohup 命令 &>>nohup.log。

数据恢复完成,指定这个实例为主库,恢复业务。后期重做从库,恢复数据同步复制。


MySQLdump一些常用参数说明


1、–all-databases , -A

含义:导出全部数据库。

示例:

mysqldump -uroot -p --all-databases

2、–add-drop-database

含义:每个数据库创建之前添加drop数据库语句。

示例:

mysqldump -uroot -p --all-databases --add-drop-database

3、–add-drop-table

含义:每个数据表创建之前添加drop数据表语句。(默认为打开状态,使用–skip-add-drop-table取消选项)

示例:

mysqldump -uroot -p --all-databases (默认添加drop语句)mysqldump -uroot -p --all-databases –skip-add-drop-table (取消drop语句)

4、–databases, -B

含义:导出几个数据库。参数后面所有名字参量都被看作数据库名。

示例:

mysqldump -uroot -p --databases test mysql

5、–events, -E

含义:导出事件。

示例:

mysqldump -uroot -p --all-databases --events

6、–flush-logs

含义:开始导出之前刷新日志。

请注意:假如一次导出多个数据库(使用选项–databases或者–all-databases),将会逐个数据库刷新日志。除使用–lock-all-tables或者–master-data外。在这种情况下,日志将会被刷新一次,相应的所有表同时被锁定。因此,如果打算同时导出和刷新日志应该使用–lock-all-tables 或者–master-data 和–flush-logs。

示例:

mysqldump -uroot -p --all-databases --flush-logs

7、–flush-privileges

含义:在导出MySQL数据库之后,发出一条FLUSH PRIVILEGES 语句。为了正确恢复,该选项应该用于导出MySQL数据库和依赖MySQL数据库数据的任何时候。

示例:

mysqldump -uroot -p --all-databases --flush-privileges

8、–force

含义:在导出过程中忽略出现的SQL错误。

示例:

mysqldump -uroot -p --all-databases --force

9、–host, -h

含义:需要导出的主机信息

示例:

mysqldump -uroot -p --host=localhost --all-databases

10、–ignore-table

含义:不导出指定表。指定忽略多个表时,需要重复多次,每次一个表。每个表必须同时指定数据库和表名。

示例:

–ignore-table=database.table1 --ignore-table=database.table2 ……mysqldump -uroot -p --host=localhost --all-databases --ignore-table=mysql.user

11、–master-data

含义:该选项将binlog的位置和文件名追加到输出文件中。如果为1,将会输出CHANGE MASTER 命令;如果为2,输出的CHANGE MASTER命令前添加注释信息。该选项将打开–lock-all-tables 选项,除非–single-transaction也被指定(在这种情况下,全局读锁在开始导出时获得很短的时间;其他内容参考下面的–single-transaction选项)。该选项自动关闭–lock-tables选项。

示例:

mysqldump -uroot -p --host=localhost --all-databases --master-data=1;mysqldump -uroot -p --host=localhost --all-databases --master-data=2;

12、–no-create-db, -n

含义:只导出数据,而不添加CREATE DATABASE 语句。

示例:

mysqldump -uroot -p --host=localhost --all-databases --no-create-db

13、–no-create-info, -t

含义:只导出数据,而不添加CREATE TABLE 语句。

mysqldump -uroot -p --host=localhost --all-databases --no-create-info

14、–no-data, -d

含义:不导出任何数据,只导出数据库表结构。

示例:

mysqldump -uroot -p --host=localhost --all-databases --no-data

15、–password, -p

含义:连接数据库密码。

16、–port, -P

含义:连接数据库端口号。

17、–routines, -R

含义:导出存储过程以及自定义函数。

示例:

mysqldump -uroot -p --host=localhost --all-databases --routines

18、–single-transaction

含义:该选项在导出数据之前提交一个BEGIN SQL语句,BEGIN 不会阻塞任何应用程序且能保证导出时数据库的一致性状态。它只适用于多版本存储引擎,仅InnoDB。本选项和–lock-tables选项是互斥的,因为LOCK TABLES 会使任何挂起的事务隐含提交。要想导出大表的话,应结合使用–quick 选项。

示例:

mysqldump -uroot -p --host=localhost --all-databases --single-transaction

19、–tables

含义:覆盖–databases (-B)参数,指定需要导出的表名。

示例:

mysqldump -uroot -p --host=localhost --databases test --tables testmysqldump -uroot -p --host=localhost --all-databases --triggers

20、–user, -u

含义:指定连接的用户名。

21、–where, -w

含义:只转储给定的WHERE条件选择的记录。请注意如果条件包含命令解释符专用空格或字符,一定要将条件引用起来。

示例:

mysqldump -uroot -p --host=localhost --all-databases --where=” user=’root’”

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多