1. 背景

为了提高系统的可用性和数据保护,MySQL通常采用master-slave的部署结构,简单高效,master和slave之间使用binlog来复制数据。

binlog支持statement和row格式,为了保证数据的一致性,通常采用row格式的event。master-slave的结构图如下:

当主库或者主库所在的主机,机房出现异常情况的时候, 进行master和slave主备切换,让slave来提供不间断的服务。主备进行切换最重要的前提就是:slave节点已经apply完毕master节点所生成的binlog,也就是slave和master处在一致的状态。

如上master-slave的结构图所示,

  1. slave端的IO thread首先接收master端生成的binlog

  2. slave端的SQL thread开始应用所接收的binlog

由于步骤1的瓶颈在于网络,通常情况下,binlog都能够很快传输到备库。

步骤2需要把row event变更到引擎中,由于是逻辑行的处理,需要索引的查找过程。

所以,大部分的瓶颈点都出在步骤2上,其决定了备库何时能够切换到主库,MySQL也一直在致力于加速binlog的apply。

接下来我们看下RDS MySQL做的一些优化。

  1. 二级索引

InnoDB Row event apply的过程, 需要根据before image进行BTree查找, 例如update row, delete row。早期只能根据pk或者uk进行索引查找,如果用户在建表的过程中,只建了普通的二级索引,在备库binlog apply的时候,需要进行全表扫描,这样,每一个row event都需要进行全表扫描,效率非常低,严重影响备库的恢复进度。

RDS在备库apply row event的时候,进行了优化,其优化原则是:

  1. 如果有pk或者uk,就直接使用

  2. 如果没有pk或者uk,就选择一个二级索引

  3. 如果没有二级索引,就选择全表扫描

这样优化后,如果二级索引的选择性比较高,apply的速度也会有很大的提升。

  1. 隐含主键

前面我们假设用户如果没有pk或者uk,而创建了普通的二级索引的情况, 如果用户没有创建任何的索引,RDS会帮助用户创建一个隐含主键,如下图所示:

这个隐含的column是auto_increment的,对用户是隐藏的,并且使用也是透明的,这样每一张表没有pk或者uk的表, 就会默认有一个隐含主键,加速备库的apply速度。

  1. 只读实例隐式提交

在只读实例上, 一方面用户进行只读查询, 另一方面sql thread进行row event apply,如果用户的查询没有自动提交来释放MDL锁,随后sql thread进行的ddl变更,就会被阻塞,导致整个apply进程被阻塞。

RDS MySQL增加了一个隐式提交的功能,针对只读实例上,用户read-committed隔离级别的sql,进行隐式提交,即在sql结束后,默认进行一个提交动作,以释放MDL锁,减少阻塞的可能性。

  1. 表级并行复制

MySQL 5.6之前, 备库的apply线程, 即SQL thread线程都是串行的在执行row event,严重影响了apply速度,在MySQL5.6之后, 官方支持了以db为维度的并行复制,也就是一个Coordinator thread,即分发线程,和一组worker thread,即工作线程。

其工作如下图:

但DB维度的并行复制,粒度太粗,所以RDS在此基础上,实现了表级别的并行复制,以加速备库恢复的并行度。