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做的一些优化。
2. 二级索引
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的速度也会有很大的提升。
3. 隐含主键
前面我们假设用户如果没有pk或者uk,而创建了普通的二级索引的情况, 如果用户没有创建任何的索引,RDS会帮助用户创建一个隐含主键,如下图所示:
这个隐含的column是auto_increment的,对用户是隐藏的,并且使用也是透明的,这样每一张表没有pk或者uk的表, 就会默认有一个隐含主键,加速备库的apply速度。
4. 只读实例隐式提交
在只读实例上, 一方面用户进行只读查询, 另一方面sql thread进行row event apply,如果用户的查询没有自动提交来释放MDL锁,随后sql thread进行的ddl变更,就会被阻塞,导致整个apply进程被阻塞。
RDS MySQL增加了一个隐式提交的功能,针对只读实例上,用户read-committed隔离级别的sql,进行隐式提交,即在sql结束后,默认进行一个提交动作,以释放MDL锁,减少阻塞的可能性。
5. 表级并行复制
MySQL 5.6之前, 备库的apply线程, 即SQL thread线程都是串行的在执行row event,严重影响了apply速度,在MySQL5.6之后, 官方支持了以db为维度的并行复制,也就是一个Coordinator thread,即分发线程,和一组worker thread,即工作线程。
其工作如下图:
但DB维度的并行复制,粒度太粗,所以RDS在此基础上,实现了表级别的并行复制,以加速备库恢复的并行度。