海山数据库(He3DB)技术分享:海山MySQL 5.7版本GTID丢失问题分析及解决方案

本文将从源码层面分析MySQL 5.7因异常重启导致整个binlong中GTID丢失问题,并逐步梳理出MySQL 5.7中GTID持久化和初始化的过程。本问题的复现步骤、产生的原因、修复方案均以BUG和patch的方式反馈给了社区,percona社区已经在其5.7版本中merge了我们贡献的patch代码。

注:本文以MySQL 5.7.44版本源码进行分析和梳理,且只针对主库。

一、问题现象

在主备实例环境中,主库重启之后,备库同步报错,备库的GTID要比主库多很多。进一步排查发现主库的GTID存在丢失,具体表现如下:

  1. mysql.gtid_executed表的GTID信息没更新

  2. global.gtid_executed没有更新

  3. 倒数第二个binlog日志中没有Previous-GTIDs信息,且大小为123kb

  4. 最新产生的binlog中的Previous-GTIDs和最近一个含有Previous-GTIDs的值相同

  5. 最新产生的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值