作者:xuty。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 1400 字,预计阅读需要 5 分钟。
生产环境中 MySQL 5.7.26 版本下,当主库短时间内连续遇到 2 次 Crash 的特殊场景时,会导致备库重新建立复制时会抛出错误 Slave has more GTIDs than the master has
,IO 线程复制报错。
2023-12-11T10:32:43.433707+08:00 1457551 [ERROR] Error reading packet from server for channel '': Slave has more GTIDs than the master has, using the master's SERVER_UUID. This may indicate that the end of the binary log was truncated or that the last binary log file was lost, e.g., after a power or disk failure when sync_binlog != 1. The master may or may not have rolled back transactions that were already replicated to the slave. Suggest to replicate any transactions that master has rolled back from slave to master, and/or commit empty transactions on master to account for transactions that have been (server_errno=1236)
如下图所示,备库比主库多了几十万个 GTID,且解析对应 Binlog 都是业务操作,更像是主库把这部分 GTID “丢掉了”。
先来复习下 MySQL 5.7 GTID 持久化原理:
GTID SET
,表示数据库中执行了那些 GTID,会实时更新,但是一旦重启就会丢失。show slave status
的 Executed_Gtid_Set
和 show master status
中的 Executed_Gtid_Set
都来自于这个变量。GTID SET
记录到表中,所以该表中会记录所有历史 binlog 中的 GTID SET
。📌当 MySQL 启动时,会初始化
gtid_executed
变量。通过读取mysql.gtid_executed
表的持久化记录(已持久化的 GTID),再加上扫描最后一个 binlog 的 GTID(未持久化的 GTID)合并后完成初始化,新的 GTID 会基于gtid_executed
变量递增产生。
基于上述 GTID 持久化的原理,我们就有理由怀疑是主库没有持久化最后 1 个 binlog 中的所有 GTID,导致备库比主库多了很多的 GTID。
通过对比 Crash 前后的 binlog 中 GTID,发现主库确实并没有持久化到 mysql-bin.001499
中的 binlog,导致后续新产生的 GTID 反而比之前的还小。(正常情况下 GTID 肯定是随着 binlog 越来越大的)。
基于 MySQL 5.7.26 版本,通过 GDB 调试模拟复现了上述问题现象,主库连续崩溃恢复后会丢失最后 1 个 binlog 中的 GTID,引发备库 GTID 大于主库。
先讲下原因:
#mysql5.7.26 crash启动流程
|main
|mysqld_main
|ha_recover #mysqld.cc:4256 恢复数据流程
|open_binlog #mysqld.cc:4282 生成新的binlog
|Gtid_state::save #mysqld.cc:4870 读取最后1个binlog写入mysql.gtid_executed
|Gtid_table_persistor::save
|Gtid_table_persistor::write_row
mysql-bin.000001
,mysql.gtid_executed
表为空,GTID 还未持久化。kill -9 mysql
进程模拟 OOM。MYSQL_BIN_LOG::open_binlog
(创建新的binlog) 和 Gtid_state::save
(崩溃启动过程中持久化最后1个binlog的GTID);MYSQL_BIN_LOG::open_binlog
处,此时可以看到还未产生新的 binlog。Gtid_state::save
,此时已经生成了新的 binlog,但是还未将 mysql-bin.000001
中的 GTID 持久化。Gtid_state::save
走完,就会把 mysql-bin.000001
中的 GTID 持久化到表中,这里模拟把 mysql 给 kill 了,此时产生了新的 binlog 但是还未做完 GTID 持久化。mysql-bin.000001
中的 GTID,此次启动并不会再次读取 mysql-bin.000001
中的 GTID 做持久化;所以备库会比主库多整整 1 个 binlog 的 GTID。同样的测试在 MySQL v5.7.26、v5.7.36、v5.7.44 均可以复现,说明 MySQL 5.7 中都存在该现象。而 MySQL 8.0 因为 GTID 持久化做了优化,所以不会有此类问题。
在 MySQL 5.7 版本下,因为 GTID 持久化机制的原因,当 MySQL 处于崩溃恢复阶段时,如果再次遇到 Crash,就可能会丢失最后 1 个 binlog 中的 GTID。
本文关键字:#MySQL# #主从复制# #GTID#