从MySQL 8.0故障恢复看InnoDB及Binlog角色变化
近在调试MySQL新功能发现MySQL 8.0相比5.7版本在mysqld crash recovery上有较大不同点,有必要记录下。主要包括事务gtid持久化到mysql.gtid_executed方式和InnoDB在其中发挥的作用。并延伸分析未来MySQL版本对InnoDB的定位。
MySQL 5.7故障恢复逻辑
关于这块实现,网上有很多文章,这里不再展开,结合下图直接说主逻辑:
1. MySQL扫描后一个Binlog文件file3,搜集其中的每个事务xid集合,集合中的事务均已提交; 2. InnoDB存储引擎通过redo和undo进行recovery,基于上一步传入的xid集合来决定处于prepare状态的事务应该commit还是rollback,r1中的事务commit,r2中的事务rollback; 3. 若开启gtid模式,file1和file2中的事务gtid均已写入gtid_executed表,MySQL层会扫描file3事务gtid信息,将gtid 201~280插入表中;
这里补充说明下两点: - 在步骤1中,除了收集xid集合,还会检查binlog event的完整性,如果有损坏的event,那么 会将Binlog文件大小截取/truncate到个有损坏的binlog event前; - 针对步骤3,有同学可能会问,为什么不在步骤1就收集gtid加入mysql.gtid_executed?因为那是InnoDB还没有完成数据恢复,对应的InnoDB表是不可用的;而且相比Binlog和InnoDB故障恢复,gtid系统的初始化并不是非常紧迫,因此放后执行。
MySQL 8.0开发时遇到的问题
在实现某个新功能时,需要将后一个Binlog文件truncate掉指定位置,该位置相比其有效位置会小一些。 调试时发现,虽然成功做了truncate,但终初始化出来的gtid_executed还是包含了已被truncate的那部分事务gtid。
按照MySQL 5.7的逻辑,gtid_executed的初始化是先获取mysql.gtid_executed表中的gtid集合,再逐个将后一个Binlog文件中的gtid加入到集合中。为什么还会出现已被truncate的gtid呢?
MySQL 8.0的gtid_executed表更新逻辑
背景介绍
在之前文章中,已经陆续介绍了一些MySQL 8.0的新特性,其中之一就是加入了InnoDB Clone/克隆功能,可通过点击链接详细了解。该功能需要记录克隆时数据对应的Binlog点位信息,xtrabackup等工具是通过加备份锁或FTWRL锁来获取。而克隆功能则是另起炉灶,直接改变了事务gtid的存储方式。
具体是怎么样的方式呢?WL#9211: InnoDB: Clone Replication Coordinates这个worklog进行了具体说明,简单来说,就是事务gtid会写入InnoDB的undo日志中。 由于mysql.gtid_executed表也是使用InnoDB存储引擎,这样就可以在克隆时仅拷贝InnoDB存储引擎层数据,再通过InnoDB recovery来恢复出对应的gtid_executed,进而再建立跟复制源的Binlog复制。
mysql.gtid_executed更新策略
undo日志对应事务在事务提交后,且不再有其他事务可能读取这些undo,其会被后台线程purge掉,其记录的事务gtid也就一并丢失。由于undo purge线程在事务提交后随时可能被触发,那么如果mysql.gtid_executed还是在Binlog文件rotate时才更新,就可能导致文件前一部分的gtid丢失。
为了解决这个问题,InnoDB层建立了gtid_executed表gtid更新与undo purge的协调关系,只有对应事务gtid已经写入gtid_executed,undo才能被purge。具体实现时维护一个InnoDB后台线程clone_gtid_thread,周期性持久化事务gtid到gtid_executed表,purge线程仅回收gtid已经被写入gtid_executed表的事务undo日志。
// clone0repl.cc
/** Persist GTID to on disk table from time to time.
@param[in,out] persist_gtid GTID persister */
static void clone_gtid_thread(Clone_persist_gtid *persist_gtid) {
persist_gtid->periodic_write();
}
相关文章