文章目录
本文将从源码层面分析MySQL 5.7因异常重启导致整个binlong中GTID丢失问题,并逐步梳理出MySQL 5.7中GTID持久化和初始化的过程。本问题的复现步骤、产生的原因、修复方案均以BUG和patch的方式反馈给了社区,percona社区已经在其5.7版本中merge了我们贡献的patch代码。
注:本文以MySQL 5.7.44版本源码进行分析和梳理,且只针对主库。
一、问题现象
在主备实例环境中,主库重启之后,备库同步报错,备库的GTID要比主库多很多。进一步排查发现主库的GTID存在丢失,具体表现如下:
-
mysql.gtid_executed表的GTID信息没更新
-
global.gtid_executed没有更新
-
倒数第二个binlog日志中没有Previous-GTIDs信息,且大小为123kb
-
最新产生的binlog中的Previous-GTIDs和最近一个含有Previous-GTIDs的值相同
-
最新产生的binlog中的GTID起始值和最近一个含有GTID EVENT的GTID起始值相同
注:发生上述问题的前置条件为第一次重启前的最后一个binlog中存在GTID EVENT
二、问题原因
管控问题
数据库第一次重启流程没有完成时(准确的讲是在第一次重启时没有在新产生的binlog中写入Previous-GTIDs信息),其他组件将mysqld进程强制kill。
内核源码问题
内核源码中,读取binlog(第二个GTID持久化介质)时,在反向扫描最后一个binlog时,如果此时binlog中不包含任何Previous-GTIDs和GTID EVENT,且参数binlog_gtid_simple_recovery为ON的情况下,则不会再去读取任何其他的binlog文件。
三、问题分析
问题复现
通过GDB设置断点的方式,控制mysqld的启动流程,具体复现步骤如下:
- 安装MySQL-5.7.44
具体安装步骤略
- 开启binlog和gtid
在参数文件中配置参数
log-bin=mysql-bin
server_id=1
enforce_gtid_consistency=on
gtid_mode=on
- 重启数据库
systemctl restart mysqld
- 手动生成部分GTID EVENT
create database testdb;
Use testdb;
Create table t1(id int, name varchar(20));
Insert into t1 values(1,’test01’); ---可多条重复执行
- 关闭数据库
systemctl stop mysql
- GDB启动mysqld并设置断点
gdb --args /usr/sbin/mysqld
b MYSQL_BIN_LOG::open_binlog
b MYSQL_BIN_LOG::init_gtid_sets
b Gtid_state::save
run
注:在MYSQL_BIN_LOG::open_binlog执行完成之后,在Gtid_state::save完成之前kill掉mysqld和GDB进程


- KILL掉mysqld和GDB进程
此时会产生一个大小123KB且无Previous-GTIDs信息的binlog


- 再次启动数据库
此次重启会再产生一个新的binlog,但该binlog中的Previous-GTIDs和mysql-bin.000004中的一致,且global.gtid_executed变量中的信息亦和mysql-bin.000004中的一致,最终导致的结果是mysql-bin.000004中的GTID EVENT 全部丢失,问题复现。
源码分析
此部分只针对数据库启动的代码流程,且只关注主库的GTID初始化和各个模块中GTID更新的时机,因为GTID的更新在很多场景下都会进行,比如:purge binary、flush logs等,而备库的更新流程则还跟参数log_slave_updates有关。
binlog创建
MySQL开启binlog,在每次重启过程中,都会执行MYSQL_BIN_LOG::open_binlog函数创建一个新的binlog文件,并初始化standard header信息,但不包括Previous-GTIDs信息。在mysqld.cc中通过if (opt_bin_log)进入,关键步骤如下:
(1) 初始化新binlog的文件名,如:mysql-bin.000010
init_and_set_log_file_name(log_name, new_name)
(2) 在binlog目录下生成新的binlog文件,此时在文件系统上就可以看到生成的文件
if (open(
#ifdef HAVE_PSI_INTERFACE
m_key_file_log,
#endif
log_name, new_name))
{
#ifdef HAVE_REPLICATION
close_purge_index_file();
#endif
DBUG_RETURN(1); /* all warnings issued */
}
(3) 将standard header信息写入到binlog文件,但不包含Previous-GTIDs信息
if (flush_io_cache(&log_file) ||
mysql_file_sync(log_file.file, MYF(MY_WME)))
goto err;
(4) 将新生成的binlog文件名写入到mysql-bin.index
add_log_to_index((uchar*) log_file_name, strlen(log_file_name),
need_lock_index))
注:该函数完成之后,在binlog存放的目录下可以看到生成了一个不含Pr

最低0.47元/天 解锁文章
1713

被折叠的 条评论
为什么被折叠?



