MySQL 实战 45 讲
林晓斌
网名丁奇,前腾讯云数据库负责人
224874 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
实践篇 (37讲)
特别放送 (1讲)
结课测试 (1讲)
MySQL 实战 45 讲
15
15
1.0x
00:00/00:00
登录|注册

26 | 备库为什么会延迟好几个小时?

优点与缺点
WRITESET_SESSION并行复制流程图
优点与缺点
WRITESET并行复制流程图
优点与缺点
按库并行复制流程图
优点与缺点
冲突检测规则
按行分发流程图
优点与缺点
冲突检测规则
按表分发流程图
并行压测线程对吞吐量的影响
InnoDB引擎的行锁支持
选择合适的并行复制策略
减少大事务操作
调整binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count参数
MySQL 5.7.22新增的并行复制策略
基于WRITESET的并行复制策略
按库并行复制策略
按行分发策略
按表分发策略
备库执行日志的速度与并行复制能力的关系
并发度对并行复制的影响
并行复制流程图
备库执行日志速度低于主库生成日志速度
备份
查询压力
优化备库延迟的方法
MySQL 5.7.22新增的并行复制策略
不同策略的优缺点
多线程复制策略的演进过程
备库延迟的原因和影响
优化备库延迟的方法
多线程复制策略
备库并行复制能力
备库延迟原因
总结
备库延迟

该思维导图由 AI 生成,仅供参考

在上一篇文章中,我和你介绍了几种可能导致备库延迟的原因。你会发现,这些场景里,不论是偶发性的查询压力,还是备份,对备库延迟的影响一般是分钟级的,而且在备库恢复正常以后都能够追上来。
但是,如果备库执行日志的速度持续低于主库生成日志的速度,那这个延迟就有可能成了小时级别。而且对于一个压力持续比较高的主库来说,备库很可能永远都追不上主库的节奏。
这就涉及到今天我要给你介绍的话题:备库并行复制能力。
为了便于你理解,我们再一起看一下第 24 篇文章《MySQL 是怎么保证主备一致的?》的主备流程图。
图 1 主备流程图
谈到主备的并行复制能力,我们要关注的是图中黑色的两个箭头。一个箭头代表了客户端写入主库,另一箭头代表的是备库上 sql_thread 执行中转日志(relay log)。如果用箭头的粗细来代表并行度的话,那么真实情况就如图 1 所示,第一个箭头要明显粗于第二个箭头。
在主库上,影响并发度的原因就是各种锁了。由于 InnoDB 引擎支持行锁,除了所有并发事务都在更新同一行(热点行)这种极端场景外,它对业务并发度的支持还是很友好的。所以,你在性能测试的时候会发现,并发压测线程 32 就比单线程时,总体吞吐量高。
而日志在备库上的执行,就是图中备库上 sql_thread 更新数据 (DATA) 的逻辑。如果是用单线程的话,就会导致备库应用日志不够快,造成主备延迟。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

MySQL的并行复制策略是解决备库延迟问题的关键。在MySQL 5.5版本中,通过按表和按行分发策略实现多线程复制,但存在一些限制。随着版本的演进,MySQL 5.6和5.7版本新增了按库并行和基于WRITESET的并行复制策略,提高了备库的同步速度。这些策略的实现原理和优缺点都得到了详细介绍,包括对大事务的影响和参数调整的建议。此外,文章还提到了MariaDB的并行复制策略,以及备库主备延迟表现为45度线段的原因。总的来说,文章通过对MySQL的多线程复制策略进行深入分析,为读者提供了全面的技术视角和实践建议。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《MySQL 实战 45 讲》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(93)

  • 最新
  • 精选
  • jike
    置顶
    老师您好,开启并行复制后,事务是按照组来提交的,从库也是根据commit_id来回放,如果从库也开启binlog的话,那是不是存在主从的binlog event写入顺序不一致的情况呢?

    作者回复: 是有可能binlog event写入顺序不同的,好问题

    2019-01-15
    6
    33
  • 长杰
    置顶
    举个例子,一个事务更新了表 t1 和表 t2 中的各一行,如果这两条更新语句被分到不同 worker 的话,虽然最终的结果是主备一致的,但如果表 t1 执行完成的瞬间,备库上有一个查询,就会看到这个事务“更新了一半的结果”,破坏了事务逻辑的原子性。 老师这块不太明白,备库有查询会看到更新了一半的结果,t1的worker执行完了更新会commit吗?如果不commit,备库查询应该看不到吧?如果commit,就破坏了事物的原子性,肯定是有问题的。

    作者回复: 应该是说,它迟早要commit,但是两个worker是两个线程,没办法约好“同时提交”,这样就有可能出现一个先提交一个后提交。 这两个提交之间的时间差,就能被用户看到“一半事务”,好问题

    2019-01-11
    14
    58
  • 老杨同志
    置顶
    尝试回答 慧鑫coming 的问题。 老师图片的步骤有下面5步 1 redo log prepare write 2 binlog write 3 redo log prepare fsync 4 binlog fsync 5 redo log commit write 1)如果更新通一条记录是有锁的,只能一个事务执行,其他事务等待锁。 2)第4步的时候会因为下面两个参数,等其他没有锁冲突的事务,一起刷盘,此时一起执行的事务拥有相同的commit_id binlog_group_commit_sync_delay binlog_group_commit_sync_no_delay_count 3)执行步骤5后,释放锁,等待锁的事务开始执行。 所以对同一行更新的事务,不可能拥有相同的commit_id

    作者回复: 👍,你比我回复得详细,顶起

    2019-01-11
    5
    118
  • HuaMax
    课后题。关键点在于主库单线程,针对三种不同的策略,COMMIT_ORDER:没有同时到达redo log的prepare 状态的事务,备库退化为单线程;WRITESET:通过对比更新的事务是否存在冲突的行,可以并发执行;WRITE_SESSION:在WRITESET的基础上增加了线程的约束,则退化为单线程。综上,应选择WRITESET策略

    作者回复: 准确👍

    2019-01-12
    7
    92
  • 每天晒白牙
    我是做java的,看老师的这个专栏,确实挺吃力的,老师专栏的干货太多了,下面的留言也是相当有水平,质量都很高,互动也好,应该是好多DBA吧,做java的我,看的头大

    作者回复: 这几篇偏深,但确实是大家在使用的时候需要了解的, 到30篇后面的文章会偏应用哈

    2019-01-13
    18
    59
  • 慧鑫coming
    老师,有个问题,mariadb的并行策略,当同一组中有3个事务,它们都对同一行同一字段值进行更改,而它们的commit_id相同,可以在从库并行执行,那么3者的先后顺序是怎么保证不影响该行该字段的最终结果与主库一致?

    作者回复: 好问题 不过这个是不可能的哈,对同一行的修改,第一个拿到行锁的事务还没提交前,另外两个会被行锁堵住的,这两个进入不了commit状态。所以这三个的commit_id不会相同的😆

    2019-01-11
    4
    34
  • 某、人
    总结下多线程复制的流程,有不对之处请老师指出: 双1,配置为logical_clock,假设有三个事务并发执行也已经执行完成(都处于prepare阶段) 1.三个事务把redo log从redo log buffer写到fs page cache中 2.把binlog_cache flush到binlog文件中,最先进入flush队列的为leader, 其它两个事务为follower.把组员编号以及组的编号写进binlog文件中(三个事务为同一组). 3.三个事务的redo log做fsync,binlog做fsync. 4.dump线程从binlog文件里把binlog event发送给从库 5.I/O线程接收到binlog event,写到relay log中 6.sql thread读取relay log,判断出这三个事务是处于同一个组, 则把这三个事务的event打包发送给三个空闲的worker线程(如果有)并执行。 配置为writeset的多线程复制流程: 1.三个事务把redo log从redo log buffer写到fs page cache中 2.把binlog_cache flush到binlog文件中,根据表名、主键和唯一键(如果有)生成hash值(writeset),保存到hash表中 判断这三个事务的writeset是否有冲突,如果没有冲突,则视为同组,如果有冲突,则视为不同组. 并把把组员编号以及组的编号写进binlog文件中 (不过一个组的事务个数也不是无限大,由参数binlog_transaction_dependency_history_size决定组内最多事务数) 3.然后做redo log和binlog的fsync 4.dump线程从binlog文件里把binlog event发送给从库 5.I/O线程接收到binlog event,写到relay log中 6.sql thread读取relay log,如果是同一个组的事务,则把事务分配到不同的worker线程去应用relay log. 不同组的事务,需要等到上一个组的事务全部执行完成,才能分配worker线程应用relay log. 老师我有几个问题想请教下: 1.在备库是单线程下,second_behind_master是通过计算T3-T1得到, 在多线程的情况下,是怎么计算出second_behind_master的值?用的是哪一个事务的时间戳? 2.多线程复制下,如果从库宕机了,是不是从库有一个记录表记录那些事务已经应用完成, 恢复的时候,只需要恢复未应用的事务. 3.binlog延迟sync的两个参数,是延迟已经flush未sync时间。意思是让事务组占用flush时间更长, 之后的事务有更多的时间,从binlog cache进入到flush队列,使得组员变多,起到从库并发的目的 因为我理解的是加入到组是在binlog cache flush到binlog文件之前做的,如果此时有事务正在flush, 未sync,则后面的事务必须等待。不知道理解得对不

    作者回复: 上面的描述部分,writeset的多线程复制流程里面,这段需要修改下: 『2.把binlog_cache flush到binlog文件中,根据表名、主键和唯一键(如果有)生成hash值(writeset),保存到hash表中 【判断这三个事务的writeset是否有冲突,如果没有冲突,则视为同组,如果有冲突,则视为不同组. 并把把组员编号以及组的编号写进binlog文件中】』 上面中括号这段要去掉, 判断writeset之间是否可以并行这个逻辑,是在备库的coordinator线程做的。 ---- 1. 在多线程并发的时候,Seconds_behind_master很不准,后面会介绍别的判断方法; 2. 是的,备库有记录,就是show slave status 里面的Relay_Log_File 和 Relay_Log_Pos 这两个值表示的,好问题 3. ”加入到组是在binlog cache flush到binlog文件之前做的,如果此时有事务正在flush,未sync,则后面的事务必须等待“ 这句话是对的,但是我没看出这个跟前面提的两个延迟参数作用的关系^_^

    2019-01-13
    6
    29
  • linqw
    学习完这篇写下自己的理解,老师有空帮忙看下哦,备库一般会延迟分钟级别,比如主库压力比较大的时候,备库有可能会延迟小时级别,为此mysql官方提供了多种多线程复制策略 1、5.6基于库的多线程复制策略,使用hash数据库名作为key,value为多少个事务修改此数据库,使用hash来分配多线程,如果一个新事务加入进来,如果有冲突的hash,分配给此线程,如果没有冲突分配给空闲的线程,感觉实现的思路使用队列+线程池,如果线程池中没有空闲的线程,就在队列中增加事务,如果队列满,分发器阻塞,不解析binlog,分发器是生产者,线程池是消费者,基于库的多线程复制有如下优点①构造 hash 值的时候很快,只需要库名;线程的hash项也很少②binlog不需要强制指定row,statement也可以拿到库名。缺点:①如果只有一个库单线程复制,可以将其热点表分布到多个库中(不推荐使用),如果多个库的热点程度不同也会使其单线程复制。 2、基于表的多线程复制(非官方,老师实现),hash数据库名+表名作为key,value为多少个事务修改此数据表,同一个事务的多张表,在同一个线程进行处理,防止违反原子性,优点对同一个库多个热点表可以同时复制,多表负载效果很好,如果碰到热点表,比如所有的更新事务都会涉及到某一个表的时候,会使用单线程复制。 3、基于行的多线程复制,key必须是“库名 + 表名 + 唯一键的值“也需考虑唯一主键,防止唯一主键冲突(cpu的多线程调度,顺序不固定),value为修改前后key的次数,约束①表必须有主键②不能有外键③binlog格式row(表复制也一样)缺点:①大事务耗cpu②hash项多。优化可以设置阈值,如果事务修改的行大于特定值,使用单线程复制(老师自己实现)。mysql官网基于行的多线程复制,表示的是对于事务涉及更新的每一行,计算出每一行的 hash保存在writeset中,优点,①是有mysql主库写入binlog中,不需要解析 binlog 内容(event 里的行数据),节省计算量②binlog格式没要求,可以使用statement③无需扫描整个事务的binlog省内存,mysql5.7.22的多线程复制实现方式。 4、mysql5.7的多线程复制实现方式,借助于处于redo prepare到commit状态下的事务可以并行,因为执行器找引擎拿数据时,事务如果锁冲突会阻塞,无法到写redo log这一步,可以使用binlog故意延迟fsync,防止频繁写磁盘操作,不会丢失数据(redo prepar+完整的binlog事务才能提交,否则回滚),使其在备库多线程复制,主备延迟低,,但是这样有一点不好,语句的响应时间变长,感觉mysql官网故意延迟redo的fsync,在binlog write的时候(因为事务的binlog要写完整,时间较长),使其能批量提交,减少iops,感觉很巧妙

    作者回复: 👍

    2019-03-10
    20
  • Mr.Strive.Z.H.L
    老师您好: 关于COMMIT_ORDER的并行复制方案,从库根据 commit_id来判断“处于prepare和commit状态的事务”。这里我有个很大的疑惑:commit_id是什么时候加入到binlog的,又是在什么时候递增的?? ( 对于我这个问题的进一步解释: 既然commit_id是要被写入到binlog的,那么commit_id毫无疑问就是在write binlog阶段写入的。 我们知道redolog是组提交的,如果只是按照redolog的组提交方式生成commit_id,那么这个commit_id包含的并行事务数量并不够多!因为在binlog write阶段,又有事务进入到redolog prepare阶段,他们之间的commit_id是不一样的,但是他们是可以并行的。 所以commit_id什么时候递增?这个是非常关键的,我也很疑惑,commit_id到底是根据什么条件递增的?? )

    作者回复: 可以这么理解,每个事务都有两个数字表示它在执行提交阶段的时间范围, 构成区间(c1, c2). 如果两个事务的区间有交集,就是可以并行的。 这里c1是事务启动的时候,当前系统里最大的commit_id; 一个事务提交的时候,commit_id+1.

    2019-01-17
    4
    14
  • J!
    同时处于 prepare 状态的事务,在备库执行时是可以并行.复制的,是这个prepare 就可以生成了改组的commited Id吗 极客时间版权所有: https://time.geekbang.org/column/article/77083

    作者回复: 进入prepare 的时候就给这个事务分配 commitid,这个commitid就是当前系统最大的一个commitid

    2019-02-01
    9
收起评论
显示
设置
留言
93
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部