1. 简介
MySQL 5.6引入了基于schema的并行复制,即如果binlog events操作的是不同schema的对象,不是DDL,且操作的对象没有对其他schema的foreign key关联,则这些binlog events在slave上做重放的时候可以并行。slave上依然还是有一条IO线程负责从master拉取binlog并写入relay log,之前负责重放relay log的SQL线程现在作为coordinator线程,根据读取到的relay log里的binlog event,决定是否需要下发到worker线程做并行重放。worker线程的数量由slave_parallel_workers决定,对于不能下发到worker的events,coordinator会等到所有worker线程执行结束后自己重放当前日志。
基于schema的并行复制MTS(Multi-Threaded Slave)能一定程度上解决之前由于单线程重放relay log造成的备库延迟问题,但当用户的实例只有一个schema时备库延迟的问题还是不能解决,而单库多表是比较常见的情形。
MySQL 5.7先是实现了基于commit-parent的并行复制,打破了之前schema的限制,很大程度提升了备库重放日志效率。在最新代码中,并行复制进一步被优化为基于lock-interval的方式,在重放日志时相较于commit-parent方式有更大的并行度。
2. 基于commit-parent的并行复制
commit-parent方式的原理很简单,如果两个事务能在master节点同时prepare成功,说明他们之间不存在冲突,那么这两个事务可以在slave节点做并行重放。
为了能让slave知道两个事务是否同时prepare成功,master节点需要将事务prepare的时间戳记录在binlog中以供slave读取。MySQL以事务提交的时间点为分界将时间轴划分为很多小的时间片,每个事务的提交意味着当前时间片的结束,以及下一个时间片的开始;每个事务在prepare时获取当前时间片的起始时间点,作为该事务的prepare时间戳。
MySQL将时间戳实现为逻辑时间戳,是一个全局单调递增的计数器,所以每个事务在prepare时获取一个计数值,这个计数值被称作该事务的commit-parent,每个事务在commit时将这个全局计数器加1。如果两个事务拥有相同的commit-parent,则意味着这两个事务在同一个时间片里prepare成功,于是他们可以被并行地重放。所以slave端逻辑很简单,只需要判断当前事务和正在执行的事务是否拥有相同的commit-parent,然后决定能否和正在执行的事务一起执行。
一个例子如下:
Trx1 ------------P----------C-------------------------------->
|
Trx2 ----------------P------+---C---------------------------->
| |
Trx3 -------------------P---+---+-----C---------------------->
| | |
Trx4 -----------------------+-P-+-----+----C----------------->
| | | |
Trx5 -----------------------+---+-P---+----+---C------------->
| | | | |
Trx6 -----------------------+---+---P-+----+---+---C---------->
| | | | | |
Trx7 -----------------------+---+-----+----+---+-P-+--C------->
| | |