数据库内核月报

数据库内核月报 - 2026 / 03

大事务实时应用

Author: 张清瑞

MySQL使用的是逻辑复制,一个事务在主库执行结束后,会发送事务产生的binlog events到备库应用,进而保证主备的一致性。

在这种架构下,对于耗时长的大事务,会导致备库有显著的复制延迟。

逻辑复制的复制延迟可以大略分为两块:binlog events传输时间 + 备库事务回放时间。对于大事务,产生的binlog events多,且事务执行时间长,两块耗时都很显著,可以说大事务复制延迟是MySQL逻辑复制的一大痛点。

对于这一问题,业内并没有一个完善的技术解决方案,常用的临时解法是通过更改业务逻辑将大事务拆分成若干小事务来进行规避,但是在很多时候,更改业务逻辑是十分困难的,所以大事务依旧屡见不鲜,根据我们线上不完全统计,500M以上的大事务,一个月的产生量在840000次以上。基于这样一个现实,AliSQL团队推出了大事务实时应用功能,根解了大事务复制延迟问题。

大事务实时应用介绍

通过上面的分析,我们可以看到大事务导致复制延迟的根因是MySQL现有架构要求必须在主库提交大事务后,备库才能应用。因此解决这个问题的直观想法就是让备库不用等待主库,而是和主库并行执行大事务。这个思路和我们之前推出的DDL实时应用是一脉相承的,但是大事务相比于DDL,又有自己的独特性,因此我们在之前架构的基础上,针对大事务做了一些调整,从而实现了大事务实时应用功能。所以强烈建议读者在阅读本文前,再翻阅一下我们的之前对DDL实时应用功能的介绍月报,回忆一下我们的整体架构,下文我们将只介绍针对大事务的改造。

传递binlog cache event

我们知道备库是通过应用binlog event来回放事务的,因此备库要做到实时应用,主库就要保证能够实时传递binlog event。MySQL 的原生逻辑是,在事务的执行过程中,就会产生binlog event,这些binlog event会先被存储在binlog cache中;在事务提交时,binlog cache中的binlog event会被逐个读出,写入binlog文件。binlog event写入binlog文件后,会通知dump线程读取binlog文件中的binlog event,然后发送给备库,整体过程如下图所示。

image.png

在DDL实时应用功能中,我们是通过传递两份binlog event,从而保证binlog event可以实时传递到备库的。但是对于大事务,binlog event本就非常庞大,额外传递一份会造成大量的网络IO消耗,因此这种思路是不现实的。

本质上binlog cache中的binlog event和binlog文件中的binlog event除了end_log_pos以及checksum外并没有什么不同,备库是完全可以回放binlog cache中的event的,所以我们通过将binlog cache中的binlog event实时传递给备库,从而保证备库可以实时回放。

image.png

Brr_gtid_executed_log_event

备库回放顺序保证

我们知道备库回放事务需要遵循一定的顺序,从而才能保证主备是一致的。MySQL 的原生逻辑是主库在事务的提交阶段,会生成逻辑时间戳(也就是我们熟知的sequence_number和last_commit),并把逻辑时间戳保存在事务的第一个event,即Gtid_log_event中;备库的SQL线程,在读取到Gtid_log_event时,会依据逻辑时间戳,判断当前事务是否可以回放,如果可以则分配给worker线程回放,否则会进行等待,通过这样的机制,MySQL保证了备库事务回放的有序性。由于逻辑时间戳是在提交阶段才生成的,所以对于实时应用,我们显然不能依赖这样的方式来保证备库事务回放的有序性。

image

幸运的是,MySQL从5.6版本就引入了gtid用来标识事务,当前已经提交的事务的gtid集合会保存在gtid_executed变量中。对于正在执行的事务,MySQL的锁机制保证了,和他有锁冲突的事务,先于他拿锁的会在他执行前先提交,事务的gtid已经存在于gtid_executed变量中了;晚于他拿锁的事务,会在他提交后再提交,事务的gtid不会在gtid_executed变量中。image.png

因此我们将gtid_executed值保存在Brr_gtid_executed_log_event中传到备库,备库的Brr_worker线程读取Brr_gtid_executed_log_event,解析出gtid_executed,当备库的gtid_executed是传来的gtid_executed的超集时,则执行,否则等待,从而保证备库事务回放的有序性。

binlog event分片

那么在什么时候取gtid_executed构建Brr_gtid_executed_log_event呢?显然,我们不能每生成一个binlog event就构建一个Brr_gtid_executed_log_event,因为大事务产生的binlog event是非常多的,大量构建Brr_gtid_executed_log_event会导致大事务的binlog event进一步的膨胀,加剧网络IO消耗;此外取gtid_executed是要拿锁的,频繁取gtid_executed会造成主库性能的劣化。因此我们采取的方式是将大事务产生的binlog event分片,每隔一定量的binlog event构建Brr_gtid_executed_log_event。

image.png

测试效果

测试效果如下图所示,本测试使用sysbench构造包含80000000行记录,大小21G的单表,然后通过delete from sbtest删除表中全部数据。未开启BRR时,备库有持续754s的复制延迟。

image.png

BRR功能基本完全消除了大事务产生的复制延迟,保证了高可用,至此AliSQL已经解决了DDL和大事务这两类常见场景的复制延迟问题。本功能已在RDS MySQL 8.0 20260228版本灰度开放,欢迎试用。