共计 2606 个字符,预计需要花费 7 分钟才能阅读完成。
问题
最近年底,大家的数据库常常跑批量大事务,会发现复制忽然断开,报错“心跳与本地信息不兼容”:会是什么起因?
试验
咱们先来复现一下,再进行剖析。
宽油,做一对主从数据库:
咱们先造一个 500M 的空文件,下一步有用:
再制作一张大表,这里用到了之前的造表法,不同的是应用了一个 longblob 字段,让多数的几行记录就能占用很大的 binlog 空间,不便咱们前面做试验。
这里的 longblob 字段,用到了上一步咱们做的空文件,
这样咱们取得了一个行数较少,但体积很大的表。
当初起两个会话,一个事务造表 t2,一个事务造表 t3,并同时提交操作,以下举例其中一个事务:
这样就取得了一个超大的 binlog,一共 32G,前 16G 是一个事务,后 16G 是另一个事务。
小贴士
一个事务超过 binlog 的限度大小(最大 1G),就会在事务后间接切换到新的 binlog。
在同一个 binlog 中,咱们想让一个超大事务后再记录一个事务,所以让两个事务同时提交,放在同一个提交组中。
查看一下 master 上的 GTID,最初两个事务别离是 25 和 26:
上面登录到 slave 上,开始表演:
咱们先重置 GTID 和复制状态,而后骗 slave 说它曾经接到了 1-25 事务,要从 26 号事务开始传输,也就是从 32G binlog 的两头地位开始传输。
而后开始复制的 IO 线程,过十几秒,就能够看到复制报错:
查看 Error log:
和咱们想要复现的报错一样。
上面咱们来看一下原理:
这个复现中有几个因素:
- 从报错得悉,报错与心跳无关,复制线必须配置复制心跳。
- 一个 binlog 中蕴含两个事务,第一个事务超过 4G。(咱们在复现中为了不便,将第二个事务也做成了大事务,这一点不是必须的)。
- 从大事务后的地位,开始进行 binlog 复制传输。
咱们用 tcpdump 抓个包:
用 wireshark 解开抓包,找到有问题的包(这里怎么找,咱们剖析后会有办法):
咱们来剖析一下包构造,这里咱们将包的内容抄写下来,不便大家浏览:
首先浏览,https://dev.mysql.com/doc/internals/en/mysql-packet.html,理解 MySQL 的客户端网络包头构造:
将咱们的包对应下来:
其后的一位 00,是 MySQL 的 command type(https://dev.mysql.com/doc/internals/en/command-phase.html),在此没有意义,咱们将其疏忽,
再持续浏览,https://dev.mysql.com/doc/internals/en/event-header-fields.html,理解 binlog event 的头构造如下:
将咱们的包对应下来:
接下来是个字符串,显著是一个 binlog 的名字,最初四个字节(下图中用黄色标注)是 checksum,
至此咱们实现了一个心跳包的解析,并没有看出重大的问题,无妨往前再找一个心跳包看看法则:
我将重点在图中标注,就是 next\_position 的地位,在这个包中为 0xfa000557,而其下一个包中为 0x19400583,显著前面的 next\_position 比后面的 next_position 小,这个不合乎常理。
而 MySQL 的报错 heartbeat is not compatible with local info,也是在报这个问题:心跳包中的 position 不应比以后的 position 小。
那是什么导致了这个问题,咱们留神到 next_position 的字段长只有 4 字节:
也就是说,该字段最大值为 2 的 31 次方,也就是 4G,以后 binlog 的地位大于 4G 时,该字段就会溢出。也就是说,之前咱们看到的地位 0x19400583,理论丢掉了最高的一位,该当是 0x119400583。
这也就导致了 binlog event 传输时,next_position 忽然会变小,心跳机制会查看到这个变动,产生报错。
那咱们怎么解决这个问题?
目前可能的办法有以下两种:
- 别用大事务,别用大事务,别用大事务。数据库系统原本就不是为大事务设计的,总会踩到不少坑。
- 停用心跳机制,这个问题并不是心跳机制带来的问题,每个 binlog event 都会带有这个包头。只是心跳机制让问题裸露了进去。如果关掉,提出问题的心跳机制,那么复制对于网络故障就会不敏感,导致更大的问题。这种形式不举荐应用。
复盘
因为文章比拟长,咱们对逻辑进行一下复盘:
- 咱们通过抓包剖析,晓得 binlog 传输的网络包里,next_position 只有 4 个字节,最大数值为 4G。
- 咱们在 master 上做了一个超过 4G 的大事务,让 slave 从这个大事务后开始传输。此时 master 会发送一个心跳包。
- 心跳包中的 next\_position 是 log event 在 binlog 地位,因为这个地位大于 4G,会被截断,导致 next\_position 比理论的小。
slave 收到心跳包,进行检测时发现 next_position 比理论的小,进行报错。
以上只是一种容易复现问题的场景。理论应用中,master 在一段时间不发送数据包后,或者非凡触发条件,都会发送心跳包。
对于一主多从的环境,每条复制链路的心跳是独自发送的,也就会导致多个 slave 的体现会有所不同,有的 slave 会触发报错,有的 slave 因为 master 没发送心跳包而不会触发报错。
最初送上几个小贴士
1)咱们如何疾速找到有问题的包?
报错信息里曾经标记了出错的 log position 是 423626115,转换成 16 进制为:0x19400583,找到由此数据的包即可。
2)一位一位读包太麻烦了,怎么办?
好办,先找到 server_id 的十六进制模式,以此为基准往后推定位数就能够。
比方咱们的 server_id 是 19327,很容易找到基准地位。
3)报错里有一段乱码是啥?
最初这四位,是 MySQL 程序有缺点,将包中的 checksum 作为文件名输入了,对程序逻辑没有影响。
0x11 是 17,对应 ASCII 码 “device control 1 character”,键盘表达形式是 “ctrl + Q”,打印模式就是 “^Q”。
本文相干的 MySQL 的 bug 列表:
https://bugs.mysql.com/bug.php?id=101948
https://bugs.mysql.com/bug.php?id=101955
对于 MySQL 的技术内容,你们还有什么想晓得的吗?连忙 留言通知小编吧!