并行复制的几种策略:库级并行、组提交、WRITESET?performance_schema.replication_applier_status_by_worker?
Parallel replication strategies: database-level, group commit, WRITESET? performance_schema
考察要点
这道题面试官想看你是否深刻理解 MySQL 主从复制中的性能瓶颈以及对应的解决方案演进。核心考察点包括:
- 复制瓶颈的认知:是否明白传统单线程复制的瓶颈在于备库的 SQL Thread。
- 并行复制策略的演进:是否能清晰阐述从库级并行(5.6)、基于组提交的逻辑时钟并行(5.7)到基于 WRITESET 的真正并行(8.0)这三代技术的原理、优缺点和适用场景。
- 实践与监控能力:是否知道如何配置、监控(特别是
performance_schema的使用)和排查并行复制中的问题。
核心概念
MySQL 主从复制的核心瓶颈之一在于备库(Replica/Slave)应用中继日志(Relay Log)的环节。传统上,这个过程由一个单独的 SQL 线程串行执行,当主库(Source/Master)并发量很高时,备库很容易因为单线程执行能力不足而产生延迟(Lag)。并行复制就是为了解决这个问题而引入的。
1. 策略一:库级并行(Database-level Parallelism)
这是 MySQL 5.6 引入的最早的并行复制策略。
- 原理:基于 "库"(Schema)进行并发。它为每个 Schema 分配一个 Worker 线程。如果一个事务跨多个库,它仍然会在前一个事务执行完毕后才执行,以保证一致性。
- 配置:
sql1-- MySQL 5.62SET GLOBAL slave_parallel_workers = N; -- N > 0, 开启并行复制3-- slave_parallel_type 参数在 5.7 中引入,5.6 默认为库级
- 优点:
- 实现简单,相比单线程有一定提升。
- 缺点:
- 效果有限:并行度受限于 Schema 数量。如果业务压力集中在单一或少数几个库上,并行复制会退化成单线程执行,无法解决核心瓶颈。
- 跨库事务限制:跨库事务会成为同步点,降低并行效率。
2. 策略二:基于逻辑时钟的并行(LOGICAL_CLOCK)
这是 MySQL 5.7 引入的,也是 5.7 的默认并行策略,实现了真正的实例级并行。
- 原理:利用了主库的组提交(Group Commit)机制。在主库上,能够同时提交的一组事务,在备库上也可以并行应用。因为它们在主库上处于同一个 "commit group",逻辑上没有依赖关系。主库在生成
Binlog时,会为同一组提交的事务打上相同的last_committed值(可以理解为一个逻辑时间戳),备库的协调线程(Coordinator Thread)根据这个时间戳将事务分发给不同的 Worker 线程。 - 配置:
sql1-- MySQL 5.7+2SET GLOBAL slave_parallel_workers = N;3SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
- 优点:
- 真正的实例级并行:打破了库的界限,只要主库并发度高,备库就能获得很好的并行加速效果。
- 缺点:
- 依赖主库并发度:如果主库本身并发低,或者事务都是串行提交的,那么备库的并行效果会大打折扣。
- 部分场景失效:在某些情况下(如外键约束、XA 事务),为了保证数据一致性,并行度会受到限制。
3. 策略三:基于 WRITESET 的并行
这是 MySQL 8.0 引入的,是目前最先进的策略。它通过分析事务实际修改的数据行来决定是否可以并行。
- 原理:在主库上,当一个事务提交时,会计算该事务修改的所有行的主键(或唯一键)的哈希值,这个集合称为 Writeset。这个 Writeset 会被记录到
Binlog中。备库的协调线程在分发事务时,会检查不同事务的 Writeset 是否有交集。只要两个事务的 Writeset 没有交集,就意味着它们没有数据冲突,无论它们在主库上的提交顺序如何,都可以在备库上并行应用。 - 配置:
sql1-- MySQL 8.0+2SET GLOBAL slave_parallel_workers = N;3SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'; -- 8.0 仍然使用此类型名,但底层机制由 WRITESET 驱动4SET GLOBAL binlog_transaction_dependency_tracking = 'WRITESET'; -- 在主库设置,决定 binlog 中是否记录依赖信息
- 优点:
- 最大化并行度:不依赖主库的提交顺序,只关心数据本身是否冲突,挖掘了最大的并行潜力。即使主库是串行提交的两个不冲突的事务,备库也能并行执行。
- 缺点:
- 要求表有主键:对于没有主键的表,更新或删除操作会退化为表级锁,其 Writeset 会是整张表,导致与该表相关的其他事务都无法并行。因此,**所有表都必须有主键(或非空唯一键)**是发挥 WRITESET 性能的前提。
- 计算开销:在主库上计算和记录 Writeset 会带来微小的性能开销。
策略对比总结
| 特性 | 库级并行 (DATABASE) | 逻辑时钟 (LOGICAL_CLOCK) | 写集 (WRITESET) |
|---|---|---|---|
| MySQL 版本 | 5.6 | 5.7 | 8.0 |
| 并行粒度 | Schema 级 | 事务组级 | 行级(基于主键) |
| 并行依据 | 事务操作的库不同 | 主库上同时提交 | 事务修改的行不冲突 |
| 核心优点 | 实现简单 | 打破库限制 | 并行度最大化 |
| 核心缺点 | 依赖多库负载均衡 | 依赖主库并发度 | 强依赖表主键 |
4. 监控视图:performance_schema.replication_applier_status_by_worker
这个视图是诊断并行复制性能的关键。它展示了每个 Worker 线程的当前状态。
-
关键字段:
WORKER_ID: Worker 线程的 ID。THREAD_ID: 对应的系统线程 ID。SERVICE_STATE:ON或OFF。LAST_SEEN_TRANSACTION: 该 Worker 最后处理的事务 GTID。LAST_ERROR_NUMBER: 最后发生的错误码。
-
使用示例:
sql1SELECT2 WORKER_ID,3 THREAD_ID,4 SERVICE_STATE,5 LAST_SEEN_TRANSACTION6FROM7 performance_schema.replication_applier_status_by_worker8ORDER BY9 WORKER_ID;通过观察这个视图,可以判断:
- 并行度是否生效:是否有多个 Worker 处于活动状态。
- 是否存在瓶颈:如果大部分 Worker 都在等待,而少数 Worker 一直在忙,可能遇到了大事务或 DDL,阻塞了其他事务。
- 是否出错:检查
LAST_ERROR_NUMBER和LAST_ERROR_MESSAGE字段。
常见低分回答
- 混淆概念:将 LOGICAL_CLOCK 和 WRITESET 的原理搞混,比如错误地认为 5.7 的并行是基于数据行冲突检测的。
- 不知前提条件:只说 WRITESET 最好,但不知道它强依赖于所有表都必须有主键。在面试官追问“如果我的业务表就是没主键怎么办”时无法回答(正确答案是 WRITESET 效果会急剧下降,甚至不如 LOGICAL_CLOCK)。
- 回答过于笼统:只说“并行复制就是多线程复制,可以加快速度”,而无法清晰阐述不同策略的原理、演进关系和优劣。
- 纸上谈兵,缺乏实践:知道理论,但当被问及“如果开启并行复制后延迟依旧,如何排查?”时,说不出要去看
performance_schema或分析大事务、DDL 等具体步骤。 - 忽略版本差异:不清楚哪个版本支持哪种策略,或者错误地将 8.0 的特性安在 5.7 上。
工程实践与优化
-
策略选择:
- MySQL 8.0+:首选并默认使用 WRITESET。务必保证所有表都有主键。
- MySQL 5.7:使用 LOGICAL_CLOCK。可以通过调整主库的
binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count来适当增大组提交的事务数量,从而提升备库的并行度。 - MySQL 5.6:使用 DATABASE 策略,但更推荐的方案是尽快升级到更高版本。
-
参数调优:
slave_parallel_workers:通常设置为备库 CPU 核心数的 1-2 倍,但需要根据实际负载(CPU、IO)进行压测调整。并非越大越好,过多的线程会增加调度开销。slave_preserve_commit_order=1(8.0+):为了在备库上获得与主库完全一致的提交顺序(例如,为了逻辑备份的一致性),可以开启此参数。它会确保事务并行应用后,按主库顺序提交,但这可能会引入提交等待,略微影响性能。
-
瓶颈排查:
- 确认延迟:通过
SHOW SLAVE STATUS \G查看Seconds_Behind_Master。 - 检查 Worker 状态:查询
performance_schema.replication_applier_status_by_worker,看是所有 Worker 都慢,还是某个 Worker 被阻塞。 - 定位阻塞事务:如果发现某个 Worker 长时间处理一个事务,可以通过
performance_schema.events_statements_current结合THREAD_ID找到该 Worker 正在执行的 SQL 语句,判断是否为大事务或 DDL。 - DDL 影响:DDL 操作通常会阻塞并行复制,因为它需要等待所有前面的事务都执行完毕才能执行,并且会阻塞后面的事务。这是并行复制的一个“同步点”。
- 确认延迟:通过
常见追问与延伸题目
-
追问:WRITESET 听起来很完美,它有什么性能开销或潜在的坑吗?
- 回答要点 1 (性能开销):WRITESET 的确有开销。主库在事务提交时需要额外做两件事:1) 计算事务修改行的哈希值;2) 将这些哈希值(即 Writeset)写入
Binlog。这会增加主库的 CPU 消耗和Binlog的大小。不过,在大多数场景下,这点开销远小于并行复制带来的收益。 - 回答要点 2 (无主键的坑):最大的坑是无主键的表。对无主键表的
UPDATE或DELETE,MySQL 无法确定具体修改了哪些行,只能认为整个表都被修改了。其 Writeset 会退化为“全表”,导致任何其他对该表的操作都无法与之并行,严重破坏并行效果。 - 回答要点 3 (哈希冲突):Writeset 使用 XXHASH64 算法,理论上存在哈希冲突的可能(尽管概率极低)。如果发生冲突,两个本不冲突的事务会被误判为冲突,导致它们被串行执行,只是损失了一点并行度,但保证了数据不错乱,所以是安全的。
- 回答要点 1 (性能开销):WRITESET 的确有开销。主库在事务提交时需要额外做两件事:1) 计算事务修改行的哈希值;2) 将这些哈希值(即 Writeset)写入
-
追问:如果开启了并行复制,但
Seconds_Behind_Master依然很高,你会从哪些方面排查?- 回答要点 1 (区分瓶颈环节):首先要确定瓶颈是在 IO Thread 还是 SQL Thread。查看
SHOW SLAVE STATUS \G,比较Relay_Log_Pos和Exec_Master_Log_Pos。如果两者差距很大,说明 IO Thread 拉取Binlog慢,可能是网络问题或主库 IO 压力大。如果两者接近,但Exec_Master_Log_Pos远落后于主库的File和Position,说明瓶颈在 SQL Thread 应用 Relay Log。 - 回答要点 2 (分析 SQL Thread 瓶颈):既然并行复制已开,瓶颈大概率是:
- 大事务/慢SQL:一个超大事务(如一次性
DELETE或UPDATE几百万行)会长时间占据一个 Worker,并可能阻塞依赖它的其他事务。 - DDL 操作:如上所述,DDL 是一个同步点,会暂停并行应用。
- 资源瓶颈:备库的 CPU、IOPS 或内存不足,即使有多线程,整体处理能力也上不去。
- 配置不当:
slave_parallel_workers数量设置不合理。
- 大事务/慢SQL:一个超大事务(如一次性
- 回答要点 3 (排查步骤):我会按以下步骤操作:1)
SHOW SLAVE STATUS定位瓶颈线程。2) 查询performance_schema.replication_applier_status_by_worker看 Worker 状态。3) 结合SHOW PROCESSLIST和performance_schema找到卡住的 SQL。4) 检查备库的系统资源监控(CPU、iostat、free)。5) 检查 MySQL 错误日志。
- 回答要点 1 (区分瓶颈环节):首先要确定瓶颈是在 IO Thread 还是 SQL Thread。查看
-
追问:MySQL 的 MGR(Group Replication)也提到了 Writeset,它和主从复制的 Writeset 是一回事吗?
- 回答要点 1 (概念共通):是的,底层的核心概念是共通的。两者都使用 Writeset 来检测事务之间的数据冲突。Writeset 就是事务修改行的主键哈希集合。
- 回答要点 2 (应用阶段不同):这是最关键的区别。
- 主从复制 (Async Replication):Writeset 是在主库事务提交后生成并写入
Binlog,用于备库并行应用。它是一种乐观的并行化,假设事务可以并行,在备库应用时解决依赖。 - 组复制 (Group Replication):Writeset 是在事务提交前,在组内各个节点间广播,用于冲突认证 (Certification)。如果一个事务的 Writeset 与其他已认证但未提交的事务冲突,该事务会被回滚。它是一种悲观的并发控制,在提交前就解决冲突,以保证组内数据的一致性。
- 主从复制 (Async Replication):Writeset 是在主库事务提交后生成并写入
- 回答要点 3 (目的不同):主从复制的 Writeset 是为了提升备库追数据的性能。MGR 的 Writeset 是为了实现分布式事务的冲突检测和共识,是保证多写节点数据一致性的核心机制。