MySQL主从复制介绍--主从复制工作原理

前言

本文主要介绍主从复制的工作原理。阅读本文可以了解到

 1. 主从复制是怎么工作的
 2. 主同步中如何选择数据格式(RBR、SBR、MBR)
 3. 从库是如何提高数据复制效率(并行重放)

一、MySQL主从复制简介

在此引用《高性能MySQL》[1]中的介绍

复制解决的基本问题是让一台数据库服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。

复制的应用场景

主从复制的主要应用场景
 1. 不同数据中心的数据备份
 2. 负载均衡,读写分离
 3. 高可用性和故障切换
 4. Mysql升级测试

在数据库版本升级之前,先把高版本的Mysql作为备库测试,降低不兼容风


二、主从复制是怎么工作的

先上图
在这里插入图片描述
对着上图

 1. 主库把数据变更记录到binlog
    a. 在准备提交事务前,记录到binlog后,会通知存储引擎事务可以提交
    b. 按照事务提交顺序记录,而不是语句顺序记录
 2. 备库把主库上的日志复制到自己的中继日志(relay log)
    a. 备库启动IO线程,跟主库建立一个普通的客户端链接
    b. 主库启动binlog dump线程,读取binlog的事件
    c. 备库IO线程接受事件并记录到中继日志
 3. 备库读取中继日志进行回放
    a. 由SQL线程执行
在上面问题中有两个问题需要重点关注,一个是文件格式如何选择,第二个是从库回放过程中,如何知道多个事务是不是可以并行。
下面分别介绍这两个方面。

三、主从同步中如何选择数据格式

Mysql记录到binlog的日志有三种,可通过 --binlog-format=type 的方式指定。
type的取值范围如下:
 1. ROW,基于行的复制,默认复制方式。
 2. STATEMENT,基于语句的复制。
 3. MIXED,混合复制方式。

每种格式说明如下:

基于行的复制(Row Based Replication RBR)

不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了,修改成什么样了。而且不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题。缺点是会产生大量的日志,尤其是alter table的时候会让日志暴涨。

BINLOG '
hA0yShMBAAAAMwAAAOAAAAAAAA8AAAAAAAAAA1NOUwAMZGJfYWxsb3RfaWRzAAIBAwAA
hA0yShcBAAAANQAAABUBAAAQAA8AAAAAAAEAAv/8AQEAAAD8AQEAAAD8AQEAAAD8AQEAAAA=
'/*!*/;

基于语句的复制(Statement Based Replication SBR)

每一条会修改数据的sql语句会记录到binlog中。优点是并不需要记录每一条sql语句和每一行的数据变化,减少了binlog日志量,节约IO,提高性能。缺点是在某些情况下会导致master-slave中的数据不一致(如sleep()函数, last_insert_id(),以及user-defined functions(udf)等会出现问题)

BEGIN
/*!*/;
# at 173
#090612 16:05:42 server id 1 end_log_pos 288 Query thread_id=4 exec_time=0 error_code=0
SET TIMESTAMP=1244793942/*!*/;
insert into db_allot_ids select * from db_allot_ids
/*!*/;

混合方式(Mixed Based Replication MBR)[2]

以上两种模式的混合使用,一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择日志保存方式。


四、从库是如何提高数据复制效率

在《主从复制是怎么工作的》中我们知道,从库需要重放中继日志中的数据。为了确保数据和主库的一致性,Mysql早期对于中继日志的回放只能采用单线程的方式,防止并行导致的数据不一致问题。而主库的写入是可以并行的,这就导致从库存在一定的性能问题。

从库是否可以并行回放数据的基本思想是,只要在主库可以并行运行的两个事务,在从库就可以并行运行。问题就转化为,在从库得到一份包含事务运行的binlog的时候,如何判断这批事务哪些之间可以并行运行。

基于这个基本思路,Mysql推出了两种从库中继日志回放的方案[3][4]

Commit-Parent-Based Scheme WL#6314[3.a]

1、基本思想

由于Mysql使用基于锁的调度器,所有处于准备阶段并且没有提交的线程都可以在从库上并行。

为了判断哪些事务处于准备阶段并且没有提交,我们在事务进入到准备阶段时分配一个逻辑时间戳,这个逻辑时间戳表示上一个提交的事务标识。如果多个事务的逻辑时间戳相同,则表示他们的上一个事务相同,表示他们同时处于了事务准备阶段并且没有提交。在从库上只要他们的逻辑时间戳相同则表示他们可以并行执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6dIegi4j-1643169601833)(https://docs.corp.kuaishou.com/image/api/convert/loadimage?id=9141355742954423723fcACDcJvJx2olWSMuaRTA7DX3&docId=fcABvsIeIaVO5kqDxelA2BL_p&loadSource=true)]
图中每一行表示一个事务,P表示事务进入准备阶段,C表示事务提交。我们可知Trx5、Trx6可以并行,因为他们的逻辑时间戳都是C2。Trx4不可以与Trx5、Trx6并行,因为Trx4的逻辑时间戳是C1。

2、实现

在master端

在SQL引擎层,通过Lamport Clock[5]实现commit parent time-stamping。有一个全局的逻辑锁commit_clock;
在事务的准备阶段,从commit_clock获取一个逻辑时间戳,作为commit parent 存储在事务中。
在存储引擎提交之前,binlog写入之后,更新commit_clock的值。

在slave端
协调线程通过commit parent把事务进行分组,相同commit parent的分为一组,同一组的可以并行执行。

3、存在的问题

简单回忆下事务的执行过程,事务的准备阶段先获取到所有的锁,提交写入binlog之后释放锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-knsi0PHK-1643169601833)(https://docs.corp.kuaishou.com/image/api/convert/loadimage?id=9141355742954423723fcACDcJvJx2olWSMuaRTA7DX3&docId=fcABvsIeIaVO5kqDxelA2BL_p&loadSource=true)]
之前我们知道Trx5、Trx6可以并行,Trx4不可以与Trx5、Trx6并行。
在同一时段,Trx4和Trx5、Trx6分别持有他们各自的锁,事务互不冲突。所以,在slave上是可以并行执行的。所以官方对提出了Lock-Based Schema。

Lock-Based Scheme WL#7165[3.b]

1、基本思想

在同一时段,如果多个事务持有他们所需要的全部锁,则他们可以并行执行。
官方定义了***lock interval***用来表示多个事务同时持有锁的这段时间。又定了***lock interval***的开始时间和结束时间。
开始时间:最后一个锁获得的时间,可以简化理解为,事务的最后一条语句写入binlog_prepare的时间。
结束时间:第一把锁释放的时间,可以简化理解为,事务提交时间。

一组事务,如果他们的lock interval 两两之间存在交集,则这一组事务可以并行执行。

2、实现

master端

- int64 global.transaction_counter
- int64 global.max_committed_transaction
- int64 transaction.sequence_number
- int64 transaction.last_committed

- 准备阶段:
    if this is not a transaction commit:
      transaction.last_committed = global.max_committed_transaction

- 事务刷入之前:
    transaction.sequence_number = ++global.transaction_counter

- 在事务Header中,记录 transaction.sequence_number、transaction.last_committed 到binlog;

- 存储引擎提交事务之前:
    global.max_committed_transaction = max(global.max_committed_transaction, transaction.sequence_number)

开始时间:transaction.last_committed,每次事务进入准备阶段时取global.max_committed_transaction更新
结束时间:transaction.sequence_number,在事务进入刷新阶段之前更新。

slave端

- transaction_sequence: sequence_number升序的,所有正在执行事务的优先队列
- 执行事务之前: 
	wait until transaction_sequence[0].sequence_number > transaction.last_committed

3、举例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BKUgJplW-1643169601834)(https://docs.corp.kuaishou.com/image/api/convert/loadimage?id=9141355742954423723fcACDcJvJx2olWSMuaRTA7DX3&docId=fcABvsIeIaVO5kqDxelA2BL_p&loadSource=true)]
假设最开始global.max_committed_transaction=0,在每个C的部分取max(global.max_committed_transaction, transaction.sequence_number)
总结下从库收到的数据(具体过程见附录,如有问题还请指出)

Trx1的transaction.last_committed=0, transaction.sequence_number=1。
Trx2的transaction.last_committed=0, transaction.sequence_number=2。
Trx3的transaction.last_committed=0, transaction.sequence_number=3。
Trx4的transaction.last_committed=1, transaction.sequence_number=4。
Trx5的transaction.last_committed=2, transaction.sequence_number=5。
Trx6的transaction.last_committed=4, transaction.sequence_number=6。
Trx7的transaction.last_committed=5, transaction.sequence_number=7。

一组事务,如果他们的lock interval 两两之间存在交集,则这一组事务可以并行执行。

开始时间结束时间可以并行的事务
Trx1`s Lock Interval01Trx2、Trx3
Trx2`s Lock Interval02Trx1、Trx3、Trx4
Trx3`s Lock Interval03Trx1、Trx2、Trx4、Trx5
Trx4`s Lock Interval14Trx2、Trx3、Trx5
Trx5`s Lock Interval25Trx3、Trx4、Trx6
Trx6`s Lock Interval46Trx5、Trx7
Trx7`s Lock Interval57Trx6

Ps. 可以并行的任务,只代表这个任务可以和哪个任务并行,不代表一组都可以并行。比如Trx2,可以Trx1、Trx2一组并行,也可以Trx2、Trx3、Trx4一组并行。但是Trx1、Trx2、Trx3、Trx4不能作为一组并行。

从上表可以看出Trx4、Trx5、Trx6可以作为一组并行,解决了Commit-Parent-Based Scheme的问题。


五、附录

字段更新说明

  1. transaction.last_committed在事务准备阶段更新
transaction.last_committed=global.max_committed_transaction
  1. transaction.sequence_number在事务提交时更新
transaction.sequence_number=++global.transaction_counter
  1. global.max_committed_transaction在事务提交时更新
global.max_committed_transaction=max(global.max_committed_transaction, transaction.sequence_number)

Lock-Based Scheme时间线
global.max_committed_transaction=0,global.transaction_counter=0

P1
Trx1的***transaction.lastcommitted=0***, transaction.sequence_number=null。
P2
Trx2的***transaction.last_committed=0***, transaction.sequence_number=null。
P3
Trx3的***transaction.last_committed=0***, transaction.sequence_number=null。
C1
global.transaction_counter=1
Trx1的transaction.last_committed=0, transaction.sequence_number=1
max_committed_transaction=1
P4
Trx4的***transaction.last_committed=1***, transaction.sequence_number=null。
C2
global.transaction_counter=2
Trx2的transaction.last_committed=0, transaction.sequence_number=2
max_committed_transaction=2
P5
Trx5的***transaction.last_committed=2***, transaction.sequence_number=null。
P6
Trx6的***transaction.last_committed=4***, transaction.sequence_number=null。
C3
global.transaction_counter=3
Trx3的transaction.last_committed=0, transaction.sequence_number=3
max_committed_transaction=3
C4
global.transaction_counter=4
Trx4的transaction.last_committed=1, transaction.sequence_number=4
max_committed_transaction=4

C5
global.transaction_counter=5
Trx5的transaction.last_committed=2, transaction.sequence_number=5
max_committed_transaction=5

P7
Trx7的***transaction.last_committed=5***, transaction.sequence_number=null。
C6
global.transaction_counter=6
Trx6的transaction.last_committed=4, transaction.sequence_number=6
max_committed_transaction=6

C7
global.transaction_counter=7
Trx7的transaction.last_committed=5, transaction.sequence_number=7
max_committed_transaction=7

六、引用

  1. 《高性能Mysq》
  2. Mixed格式介绍
  3. MySQL5.7并行复制中并行的真正含义
    a. Commit-Parent-Based Scheme
    b. Lock-Based Scheme
  4. TimeClocks
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值