问题描述和处理
同步到的版本为5.7.35,按理说在5.7种还是一个比较新的版本了,报错大概如下:
2023-05-14T05:09:47.427031Z 12 [Note] Multi-threaded slave statistics for channel '': seconds elapsed = 163; events assigned = 67585; worker queues filled over overrun level = 0; waited due
a Worker queue full = 0; waited due the total size = 0; waited at clock conflicts = 0 waited (count) when Workers occupied = 0 waited when Workers occupied = 0
2023-05-14T05:09:48.526341Z 12 [ERROR] Transaction is tagged with inconsistent logical timestamps: sequence_number (7870797233) <= last_committed (2957740090370948108)
2023-05-14T05:09:48.526416Z 12 [ERROR] Slave SQL for channel '': ... The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state. A restart should rest
ore consistency automatically, although using non-transactional storage for data or info tables or DDL queries could lead to problems. In such cases you have to examine your data (see docum
entation for details). Error_code: 1756
从报错来看肯定属于逻辑时钟并发的问题,因为在5.6中gtid event没有last commit和seq number,因此可能并发的时候遇到了问题,因此简单的关闭了MTS并发回放就可以了继续了。
分析原因
实际上在代码中对于GTID不包含的last commit和seq number的情况,在初始化gtid event的时候应该将其初始化为0(SEQ_UNINIT= 0;
),并且并发的时候也会有相关判定如下:
那么这个条件不可能成立,因为last_committed == SEQ_UNINIT,也就不会报错,但是实际上这里报错了。从报错的seq number和last commit值来看,应该是内存越界访问的结果,否则不会有这种很大的seq number和last commit(sequence_number (7870797233) 、 last_committed (2957740090370948108))。
那么接下来就是看看为什么会出现这种情况,也就是gtid event的初始化遇到了什么问题。实际上问题就出在binary_log::Gtid_event::Gtid_event这个构造函数中,如下
这里ptr_buffer + LOGICAL_TIMESTAMP_TYPECODE_LENGTH +
LOGICAL_TIMESTAMP_LENGTH <= buffer + event_len的含义是,如果本次获取的gtid event的长度小于5.7格式的gtid event的长度就不要执行last commit和seq number的赋值,因为5.7的gtid event 包含了16字节的last commit和seq number,还包含了1字节的typecode,总共比5.6的gtid event 大17字节。
但是这里的问题在于buffer + event_len中buffer的位置已经是去掉event header的长度,也就是计算的是event_len的长度+event header的长度,如下,
而event header为19个字节,因此判定的实际上就是是17字节<=19字节,这个条件是一定会满足的,因此这里出现了buffer的越界访问,当越界访问的值也满足第二个条件的时候,就会进行last commit和seq number的赋值,但是为越界访问,因此值不确定,比如看到的如下,
当不确定的值遇到last commit比seq number还大的情况就会报错了。那么这里实际上稍微改一下
就可以了,LOG_EVENT_HEADER_LEN则为19字节。修改编译后5.6到5.7的同步可以正常运行,再次查看last commit和seq number就是0了。
这也是正常的因为5.6就没有last commit和seq number。并且还需要测试5.7到5.7的主从同步,是否gtid event能够拿到正确的值,因为如果这里过滤异常了,则5.7到5.7的同步seq number和last commit 也可能初始化为0,也就是测试修改是否正确,查看seq number和last commit如下,
能够初始化为正常的值。
提问一个问题
C语言内存越界不一定会触发SIGSEGV信号的原因是因为,SIGSEGV是由操作系统抛出的信号,当程序执行访问了无效的内存地址时,操作系统会检测到这种异常情况,并向进程发送SIGSEGV信号。
然而,在C语言中,对于数组和指针的访问并未受到强制的边界检查,如果程序发生内存越界,也许不会直接导致程序崩溃,而是可能引起一些意想不到的结果,比如修改了其他变量的值、出现奇怪的计算结果等。这种情况被称为未定义行为。
因此,当程序存在内存越界的问题时,不一定会立即引发SIGSEGV信号,而是要视具体情况而定,有时候会在操作系统的内存保护机制下触发SIGSEGV信号,有时候则不会。为了避免内存越界带来的未定义行为,应该在编写代码时尽量避免这种情况的出现,或者通过调试工具等手段进行检测和修复。