记一次MySQL数据库主从同步异常问题分析

记一次MySQL数据库主从同步异常问题分析

# 环境说明 

数据库:MySQL 8.0.20双主

记一次MySQL数据库主从同步异常问题分析
记一次MySQL数据库主从同步异常问题分析

# 问题现象 

MySQL数据库从库所在虚拟机异常宕机,虚拟机恢复后,从库SQL线程无法正常启动,报错如下:

Last_Errno: 1782Last_Error: Error executing row event: \'@@SESSION.GTID_NEXT cannot be set to ANONYMOUS when @@GLOBAL.GTID_MODE = ON.\'

记一次MySQL数据库主从同步异常问题分析
记一次MySQL数据库主从同步异常问题分析

# 问题分析 

简单回顾下主从复制过程:

记一次MySQL数据库主从同步异常问题分析

不同类型的数据库,主从复制原理大致相同,通常经历下面三个步骤:

  1. 主库日志发送;

  2. 从库日志接收;

  3. 从库日志应用。

对于MySQL数据库,主库的更新事件(update、insert、delete)会按照顺序写入binlog日志中,主库binlog dump thread读取binlog数据,并发送给从库I/O线程,从库I/O线程将接收到binlog数据写入到从库本地relaylog文件,从库SQL线程解析relaylog文件,将数据按顺序回放到从库,从而实现主从数据同步。

同理,Oracle DG同步原理也是类似,具体名称和实现方式略有差异,Oracle的RFS进程负责接收日志,类似于MySQL的I/O线程,MRP/LSP负责日志应用,类似于MySQL的SQL进程。

原理了解以后,再回到一开始的问题,数据库从库所在虚拟机异常宕机,恢复后,MySQL从库I/O线程、SQL线程是如何自动恢复的,这时面临两个问题:

(1)I/O线程如何找到最后一次接收主库数据的位置信息,进行数据的断点续传。

(2)SQL线程如何找到宕机前最后一次应用relaylog日志的位置信息,进行断点数据的继续应用。

I/O线程断点续传方式如下:

从库的I/O线程通过读取mysql.gtid_executed表以及binlog文件获取Executed_Gtid_Set,通过读取realy_log文件获取Retrieved_Gtid_Set,并将三者的并集发送到主库请求binlog。

(1)mysql.gtid_executed

mysql.gtid_executed记录从库执行过的事务开始ID(UUID、开始事务ID、结束事务ID)。

记一次MySQL数据库主从同步异常问题分析

(2)binlog

例如:

记一次MySQL数据库主从同步异常问题分析

通过上面查到的mysql.gtid_executed、binlog、relaylog取并集发送到主库,并和主库的gtid做比较。

主库查询方式:

Select * from mysql.gtid_executed;show global variables like \'GTID_EXECUTED\';show master status\\G;

具体从库发给主库的GTID:

UNION(@@global.gtid_executed, Retrieved_gtid_set - last_received_GTID)

为什么需要减去last_received_GTID?

因为I/O线程读取主库binlog时,是以event为单位进行读取的,而一个事务包含了一个event组,当突然执行stop slave或者其他异常行为时,可能导致一个事务只读取了部分event,如果不减去这个binlog读取不完整的GTID,有可能导致SQL线程复制报错终止。

名词解释:

Retrieved_Gtid_Set:表示slave从master接收的gtid set。

Executed_Gtid_Set:表示slave已执行的gtid set。

last_received_GTID:表示最后一个事务信息。

最终:

将主库的GTID事务和从库事务做差集就是从库没有接收到的事务,主库会将这部分事务对应的binlog数据发送给从库,从库接收后写入自己本地的relaylog日志。

例如:

主库存在1,2,3,4,5个事务,从库存在1,2,3,4个事务,经过比对后,主库会将事务5对应的binlog数据发送给从库。

SQL线程断点续传方式如下:

对于备库的SQL线程其应用relay log的起始从mysql.slave_relay_log_info表读取,slave_relay_log_info表记录了SQL线程工作位置信息,它会把值传给”show slave status”中的Relay_Log_File、Relay_Log_Pos。

slave_relay_log_info表数据来源:

当relay_log_info_repository=TABLE时,apply event和更新slave_relay_log_info表是在一个事务内完成的,也就是事务提交信息同步到slave_relay_log_info表。

mysql> select * from mysql.slave_relay_log_info\\G;

例如:

记一次MySQL数据库主从同步异常问题分析

备库通过查询mysql.slave_relay_log_info表,知道了宕机前最后一次读取到的文件是mysql-relay-bin.000002,位置信息是19221,那么SQL线程就会从这个位置开始应用数据回放到从库。

下面回到最开始的问题,SQL线程启动报错1782:

启动SQL线程,报错1782:

Last_Errno: 1782Last_Error: Error executing row event: \'@@SESSION.GTID_NEXT cannot be set to ANONYMOUS when @@GLOBAL.GTID_MODE = ON.\'

SQL线程在读取指定relaylog日志,回放数据时发生了错误,通过报错信息,从库启用了GTID复制,在应用数据时发现存在匿名事务,导致失败。

通过mysqlbinlog分析relaylog日志数据:

mysqlbinlog --base64-output=decode-rows --verbose mysql-relay-bin.00xxx

部分内容如下:

记一次MySQL数据库主从同步异常问题分析

at 364  出现 Row event for unknown table #99027

还有一个没有分配GTID的commit,被SQL线程识别为匿名事务,从而导致SQL线程启动失败。

正常的事务

应该具有明确的GTID.NEXT、BEGIN、COMMIT信息。

例如:

记一次MySQL数据库主从同步异常问题分析

记一次MySQL数据库主从同步异常问题分析
记一次MySQL数据库主从同步异常问题分析

# 故障原因 

relaylog日志中为什么会出现不完整的事务,经分析,导致1782错误可能的原因如下:

(1)BUG

在5.7之前的版本,同一个事务不能跨多个binlog,但是可以跨越多个relaylog。

这意味着mysql在读取以半事务开始的中继日志时会生成类似于1782的错误。

参数max_relay_log_size限制单个relaylog大小,当达到最大大小,会自动切换到新的relaylog。

但是5.7开始,同一个事务也不会跨多个relaylog,当有一个大事务发生时,relaylog大小会超过max_relay_log_size。

当前数据库版本8.0.20,经测试,不存在这个BUG导致。

(2)GTID复制模式并不是一开始就启动的

例如,一开始是传统复制模式,后来通过命令转换为GTID复制模式。

参考:甲骨文官网知识库 https://support.oracle.com 文章编号 2807183.1

记一次MySQL数据库主从同步异常问题分析
记一次MySQL数据库主从同步异常问题分析

原因:

记一次MySQL数据库主从同步异常问题分析

当前数据库GTID复制模式已经运行很长时间,并不是刚刚转换成GTID模式。

(3)relaylog损坏

当前relaylog部分内容损坏,导致SQL线程无法正常解析relaylog日志,本次故障初步怀疑文件损坏导致的。

记一次MySQL数据库主从同步异常问题分析
记一次MySQL数据库主从同步异常问题分析

# 解决方案 

官网知识库提供的解决方案如下:

记一次MySQL数据库主从同步异常问题分析

具体解决步骤如下:

参考:甲骨文官网知识库 https://support.oracle.com 文章编号 2807183.1

从库:

(1)停同步

stop slave;

(2)清空同步信息

reset slave all;

(3)重新配置主从连接

CHANGE MASTER TOMASTER_HOST=\'主库IP\',MASTER_USER=\'XXXX\',MASTER_PASSWORD=\'******\',MASTER_PORT=XXXX,MASTER_AUTO_POSITION=1;

(4)启动同步

start slave;

(5)验证

show slave status\\G;

经验证,此方法确实可以解决测试复现后的1782问题。

当然,如果此方法仍然部分解决1782问题,也可以考虑重新初始化从库。

记一次MySQL数据库主从同步异常问题分析
记一次MySQL数据库主从同步异常问题分析

# 问题思考 

为什么重启slave不能解决1782错误,change master创建主从关系可以解决呢?

原因是两种方式在启动slave时,查找复制位置方式不同。

通过重启slave查找复制位置:

(1)从库mysql.gtid_executed表、binlog文件、relaylog文件中取出gtid事务信息和主库gtid事务做比较,差集对应的binlog数据需要重新从主库拉取。

(2)然后在根据mysql.slave_relay_log_info的位置信息开始应用relaylog日志。

通过change master查找复制位置:

因为change master后又执行了reset slave all,清空了本地所有的relaylog日志,同时清空了show slave status\\G中的Retrieved_Gtid_Set信息(记录slave从master接收的gtid set)和mysql.slave_relay_log_info表数据,但不会清空mysql.slave_relay_log_info信息(记录slave已执行的gtid set),除非执行reset master;

从库SQL线程位置为起点重新向主库拉取binlog日志并生成新的relaylog日志,从而避免了从库因relaylog损坏或不完整导致的复制中断问题。

(1)从库根据mysql.slave_relay_log_info的位置信息拉取主库的binlog数据。

(2)然后在根据mysql.slave_relay_log_info的位置信息开始应用relaylog日志。

如何避免此类问题再次发生?

如何避免从库异常宕机,并且从库当前relaylog损坏导致复制中断?

需要改变从库启动slave拉取binlog或relaylog的方式:

由原来的:以I/O线程位置为起点向主库拉取binlog方式。

改成:以SQL线程位置为起点向主库拉取binlog方式。

优点:

避免了因relaylog损坏导致复制中断的问题。

缺点:

当SQL线程速度远远落后于I/O线程时,以SQL线程位置为起点向主库拉取binlog方式需要拉取更多的binlog日志,速度较慢。

例如:

主库执行了1,2,3,4,5共五个事务。

从库I/O线程已经拉取完成了1,2,3,4共四事务。

从库SQL线程应用了1,2共两个事务。

这时如果从库异常宕机,启动后,会从事务3开始重新拉取。

如何更改这种方式?

受参数relay_log_recovery控制,默认OFF。

当启用relay_log_recovery后,会变成以SQL线程位置为起点向主库拉取binlog方式。

此参数为只读参数,不支持在线修改,更改完配置文件后,重启数据库生效。

实际上,早在MySQL5.6就已经提供了crash safe slaves功能。

该功能主要解决之前版本中系统异常断电等场景可能导致的I/O、SQL线程信息不准确问题。

那么哪些场景可能会出现I/O,SQL线程信息不准确?

1.relay log损坏,导致SQL线程失败,可以通过relay_log_recovery参数解决。

2.binlog位置信息不准确,当master_info_repository为FILE时,binlog位置信息不是实时记录的,因为表数据更新和磁盘FILE文件更新很难做到实时同步,需要将master_info_repository改成TABLE,也就是将binlog位置信息记录到数据库表里,这样就可以将业务操作和更新binlog信息的操作放到一个事务里,从而解决数据不一致的问题。

3.和第2点类似,relaylog位置信息也可能不准确,也需要将relay_log_info_repository改成TABLE。

除此以外,sync_binlog、sync_relay_log、innodb_flush_log_at_trx_commit等参数也会影响数据的准确性,当然参数的安全级别越高,性能通常也是最差,实际使用过程中,需要结合每套库的特点,选择适合的参数。

主从复制源码解析:

由于篇幅有限,MySQL主从同步还有很多细节没有讨论,例如Relay_log_info记录了哪些信息,如何更新,更新触发条件等,如果感兴趣可以下载源码进行深入研究。

获取MySQL源码方式:

官网下载指定版本源码安装介质,例如MySQL 8.0.22:

记一次MySQL数据库主从同步异常问题分析

解压后,进入sql目录下

记一次MySQL数据库主从同步异常问题分析

Relay_log_info相关信息,可以查看rpl_rli.cc文件,部分内容如下:

记一次MySQL数据库主从同步异常问题分析

记一次MySQL数据库主从同步异常问题分析

E N D –

文章作者:陈举超

手绘插画:岳   媛

原创文章,作者:EBCloud,如若转载,请注明出处:https://www.sudun.com/ask/32573.html

(0)
EBCloud's avatarEBCloud
上一篇 2024年4月2日 下午3:28
下一篇 2024年4月2日 下午3:28

相关推荐

  • 如何提高网站排名?SEO关键词排名优化技巧

    想要让自己的网站在搜索引擎中脱颖而出,吸引更多的流量和用户?那么就不能错过今天要介绍的内容——如何提高网站排名?SEO关键词排名优化技巧。通过本文,你将会了解到什么是搜索引擎优化,…

    行业资讯 2024年3月24日
    0
  • 紫菜头博客怎么应对dns污染攻击?

    作为互联网行业的从业者,我们都知道DNS污染攻击是网络安全中的常见问题。近日,紫菜头博客也受到此次攻击影响,导致网站无法访问。那么什么是DNS 污点攻击?它对Nori 博客有何影响…

    行业资讯 2024年5月6日
    0
  • 福田做网站

    福田做网站,这听起来似乎是一件很简单的事情。但是,随着互联网的快速发展,传统的网站建设已经无法满足现代企业的需求。而云服务器作为一种新兴的网络基础设施,却给网站建设带来了全新的变革…

    行业资讯 2024年4月5日
    0
  • 服务器使用什么防御最有效?

    在当今数字化时代,网络安全问题已经成为各行各业必须面对的挑战。特别是在服务器使用方面,如何有效防御不断增长的安全威胁已经成为了网络安全加速行业的重要议题。随着技术的不断发展,服务器…

    行业资讯 2024年4月4日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注