数据库内核月报

数据库内核月报 - 2025 / 07

用了RDS MySQL,我的半同步复制终于不超时了!

Author: 武根泽(子堪)

什么是半同步复制

从 MySQL 5.5 开始,除了经典的binlog异步复制,官方还提供了半同步复制这个选项,他们的示意图如下:

image.png

上图中左边是异步复制,事务提交时只管binlog写入,后续的binlog发送是异步进行,不影响主库业务;上图右边是半同步复制,事务提交写入binlog后,需要等待binlog完全发送,从库回复确认后才能完成事务提交。

理想情况下,使用半同步复制后,只要你的commit语句执行成功了,这个事务就一定已经传送到从库,即便此时主库发生故障,ha切换后这个事务也不会丢失。但是在现实中,半同步复制在一些场景会退化成异步复制,提供不了上述保证,其中半同步超时就是最常见的一种退化场景。

什么是半同步超时

一个事务在半同步复制实例上执行,会比在普通实例执行多等待一个 binlog 发送和确认,如果这个过程耗时出现抖动,半同步实例的性能就会出现抖动。

为了防止抖动时间超出业务容忍极限,半同步复制引入了超时退化机制,由参数rpl_semi_sync_master_timeout 来控制。当主库上的事务等待 binlog 发送和确认的时间超过了这个参数的配置时,半同步复制就会退化,事务就会立刻继续提交,不再继续等待;后续的其他事务也不会再等待确认,直到从库回复的ack追上主库的binlog位点,才会重新恢复半同步复制。

官方rpl_semi_sync_master_timeout的默认值为10秒,10秒的不可写对很多业务来说是无法接受的。RDS MySQL上rpl_semi_sync_master_timeout 参数可以控制台配置,默认值为1秒。

半同步复制的性能抖动问题

为了避免严重的性能抖动,MySQL引入了半同步超时机制,虽然保护了业务可用性,却对数据可靠性造成很大影响;而且超时机制只能避免较严重的性能抖动,对于轻微的性能抖动没有触发超时的,还是会造成大量慢sql。

那性能抖动有没有别的方式去避免呢?答案是有的。造成半同步性能抖动的场景主要有以下两个,我们逐个分析一下。

网络问题

如果网络抖动,会导致binlog发送和确认变慢,进而引起性能抖动。这个场景在RDS MySQL上基本不会出现,阿里云国内同城的机房间走的是专线,网络非常稳定。

大事务

大事务是造成半同步超时的另一个常见因素,其原理如下图所示。

在半同步复制的主从之间,只有一个binlog传输线程,它是按照事务在主库binlog中的顺序,将事务一个一个复制到从库。如果主库上提交一个大事务,传输线程会耗费大量的时间传输这个大事务,在此期间其他小事务都不能传输,实例会出现一段时间的不可写状态,不可写时间 = 大事务binlog大小 / 传输速度

除了超时之外,还有其他办法能解决这个问题么?比较直观的解法有两种,要么将大事务打散,分成多个小块存在binlog中;要么干脆限制事务binlog大小,超过大小的事务直接报错。这两种方法前者会影响依赖binlog生态的下游组件,后者对业务的影响太大,都不适合RDS MySQL,有没有更好的办法?有的!

RDS MySQL 不超时!

在RDS MySQL上,我们设计了binlog实时传输功能,在不修改binlog内容,不影响生态的前提下解决大事务半同步超时问题。

在MySQL中,事务执行过程中会不断生成binlog event,并暂存在binlog cache中;提交时,事务线程会将这些event全部写入binlog文件。

大事务执行时间长,产生的binlog event多,我们可以在执行期间,就从binlog cache中读出这些event,把它们实时的发送到从库,暂存在relay log cache中;当主库上事务提交时,会告知从库将relay log cache转化为relay log文件(耗时很短),从库完成后即可回复确认。如果主库上事务回滚,会告知从库将relay log cache删除掉。

实时发送大事务的binlog event可以使用一个额外的传输线程;也可以使用原有传输线程,将event分成多个小块传输。这两种方式都可以避免大事务长期占用传输线程,阻塞其他小事务传输,进而避免实例半同步性能抖动甚至超时。

Relay Log Cache的设计

为了在从库上暂存大事务实时发送的binlog event,我们给每个事务开启一个relay log cache,在写relay log cache时,需要在头部预留一部分空间,以方便后续将relay log cache转化为relay log文件,如下图所示。

image.png

预留空间可能有富余,也可能不够。当预留空间有富余时,使用一个empty event去填充这个剩余的空间;当预留空间不够时(gtid executed空洞非常多,正常情况不会发生),会回退到原有逻辑。

效果测试

为了测试功能的效果,我们在一个半同步复制实例上,用sysbench的oltp_write_only脚本维持一个长稳压力,随后提交一个2GB的大事务。

image.png

可以看到在原生MySQL上,性能先抖动跌零;然后半同步超时,数据可靠性降低,性能上涨;随后恢复回半同步复制。而RDS MySQL优化后,实例始终平稳运行(注:大事务提交写binlog也有性能抖动,要测出完全平稳运行的效果,需要开启RDS MySQL的另一个优化:Binlog cache free flush功能)。

本功能已取得国家发明专利(202510139327.5),并且已在RDS MySQL 8.0 20250531版本灰度开放,欢迎试用。