作者简介:高鹏,笔名八怪。《深入理解MySQL主从原理》图书作者,同时运营个人公众号“MySQL学习”,持续分享遇到的有趣case以及代码解析!
我们知道一个事务的binlog一定在一个binlog里面,其实一个group commit的事务都应该在一个binlog里面,那么很可能这个binlog的大小会大于4G。可以参考这篇文章:https://www.jianshu.com/p/10082cb72c42 截取部分解析如下:
事实上在第10步中我们只是设置了切换标记而已,实际的切换会等到本事务所在的commit队列都提交完成后才会进行binlog的切换,具体就是参考第28步。
在这个期间会有2个原因导致大事务并不是binlog的最后一个事务:
对于flush队列而言,大事务可能包含在队列中的某个位置,队列后面可能包含小事务。
对于sync队列而言,大事务的提交会在sync阶段耗费很多时间,如果我们假设为30秒,那么在这30秒内其他新的事务是可以进入新的flush队列的,也能够进行写binlog(不是fsync)的操作。
因此线上有压力的库,binlog的最后一个事务通常不是大事务。
那么考虑这个问题,主要是因为,而且某些其他工具比如OGG等会遇到问题。
- binlog内部的event的end log pos为4字节,如果binlog大于4G如何处理?
- 同时主从协议中可能位点的存储为4字节,如果binlog大于4G如何处理?
很显然uint32 最大值就是4G,那么主从该如何处理,而end log pos如何存储呢?
一、测试图
由于我本机已经没有那么大的空间进行测试了,因此沿用老的一张朋友的测试图如下:
我们这里可以看到随着binlog的增大,binlog的大小已经超过了4G,但是end log pos直接重新循环为3601大小。
二、end log pos为什么重新循环了
大概这个写入路径,当我们执行提交到 flush阶段的时候会写入我们的binlog,我们稍微看看end log pos的计算方式:
uint32 end_log_pos
// Increase end_log_pos
end_log_pos+= *event_len_p;
这里应该是溢出了,也就是将uint64截取了剩下的4个字节直接赋予给了end_log_pos(uint32),我们直接验证一下如果,我将uint64的4294970897赋予给uint32的变量如下:
[root@mgr4 ~]# ./a.out
3601
我们可以发现和第一节超过4G后的end log pos一致了。这应该是end log pos重新循环的原因。但是这个值是在show slave status的时候读取和执行主库binlog position的位点,当前不知道show slave status是否会出现问题,这个也比较容易测试,做一个超大的事务大于4G就可以测试出来。当前翻阅的逻辑暂时没有找到如何处理。
三、主从协议postition为4字节的问题
这个问题考虑一个情况,如果我们大于4G的部分包含多个事务,也就是一个group commit,如下:
事务1、事务2、事务3位组提交,包含在一个binlog里面
事务1 -->大于4G
事务2 -->从库crash
事务3
这个时候如何指向事务2和事务3的位置继续。这个时候看起来基于传统position的方式是硬伤。代码注释如下:
com_binlog_dump(传统基于位点的主从)
/*
4 bytes is too little, but changing the protocol would break
compatibility. This has been fixed in the new protocol. @see
com_binlog_dump_gtid(基于GTID AUTOPOSITION的主从).
*/
也就是说我们应该使用GTID AUTOPOSITION来避免这个问题,我们可以看到确实也有这种大于4G的考虑在里面。如果是GTID字段寻址,然后将得到的位点保存在一个uint64的变量中,结合event中的event len即可继续读取。寻址的起始位点如下(dump线程读取event见下一节):
unsigned long long
my_off_t start_pos
看来我们真的应该多使用GTID AUTOPOSITION方式了。
四、DUMP线程读取
前面是初始化寻址存储在unint64变量中,一旦我们寻址成功了,那么接下来就是循环读取event了。这里使用的是start_pos+event len的方式来不断的向下读取,意思就是说如果start_pos正确则可以继续读取了。大概如下(Log_event::read_log_event):
(my_b_read(file, (uchar*) buf, LOG_EVENT_MINIMAL_HEADER_LEN)) //读取event header
data_len= uint4korr(buf + EVENT_LEN_OFFSET);
my_b_read(file,reinterpret_cast(event_data_buffer),data_len);
这里我们看到读取了event的header 然后获取了data len,然后file里面包含了start_posgitd也就是通过找到的起始位点,那么就可以依次读取到event了。这里实际上也不基于end log pos,因此DUMP可以正常读取event即便大于4G。
五、未尽事宜
- 对于大于4G的binlog主从正常的情况下,show slave status如何显示?
- 可以限制事务的最大binlog量通过max_binlog_cache_size,默认4G,看来也是有这些原因在里面。
- 其他限制?