MySQL并行复制调优最佳实践:从LOGICAL_CLOCK到WRITESET_SESSION的升级路径

简介: 本文详解MySQL并行复制四大演进阶段(DATABASE→LOGICAL_CLOCK→WRITESET→WRITESET_SESSION),聚焦主从延迟根因,结合MTS参数调优、worker数设置、四类延迟场景排查及业务层优化(拆大事务、心跳表监控等),助你彻底解决Seconds_Behind_Master飙升难题。

📌今日关键词:MySQL并行复制、主从延迟、LOGICAL_CLOCK、WRITESET、MTS调优

大家好,我是数据库小学妹 👋

之前我们聊过主从同步失败的五大诱因,有粉丝朋友问我:"小学妹,我的从库倒没挂,但 Seconds_Behind_Master 一直飙到几千秒,延迟越来越大怎么办?"

这个问题我太有发言权了。虽然我们讲过主从延迟的5个常见原因和3个排查命令,但那只是入门级别。真正要解决"从库追不上"的问题,得搞懂并行复制这个核心机制。

我一开始以为并行复制就是改个参数的事,设个 slave_parallel_workers 就完事了。结果改完延迟一点没降,还差点搞出数据不一致。后来才明白,MySQL 的并行复制经历了四代演进,每一代的设计思路完全不同,参数之间还有依赖关系。不搞清楚原理就上手调,跟闭眼开车差不多。

今天从单线程为什么慢讲起,把四代演进拆透,再配合排查路径和调优方案,争取看完就能上手。


一、为什么从库会"追不上"主库?

先回忆一下主从复制的基本流程:

主库写入 → binlog → 从库IO线程拉取 → relay log → 从库SQL线程重放 → 数据写入

延迟就卡在最后一步:SQL线程重放。

主库能同时处理几百上千个并发连接,binlog里记录的是所有连接混合在一起的事务。但从库呢?MySQL 5.6之前,只有一个SQL线程在那一条一条地重放。

打个比方:主库是10条流水线同时干活,从库只有1个人在那一件一件地做。主库写得越快,从库就落得越远。

说白了,就是干活的人太少。网络没问题,磁盘也没问题,瓶颈出在重放线程上。


二、并行复制的四代演进

MySQL团队为了解决这个瓶颈,前前后后搞了四代方案。每一代都是在上一代的基础上改的。

第一代:基于Schema的并行(MySQL 5.6)

5.6引入了最早的并行方案:如果两个事务操作的是不同数据库(schema),它们之间没有数据冲突,可以并行执行。

# my.cnf
slave_parallel_workers = 4
slave_parallel_type = DATABASE   # 基于数据库并行

局限性很大。现在的业务谁不是一个库搞到底?单库多表的场景下,这个方案基本等于没开。十个表都在同一个库里,全部串行。

我当时的项目就是这种情况,一个业务库里40多张表,开了这个参数之后延迟纹丝不动。

第二代:LOGICAL_CLOCK(MySQL 5.7.2+)

这是真正意义上的突破。思路变了:不再看"是不是同一个库",而是看"在主库是不是一起提交的"。

核心机制叫 Group Commit(组提交)。

什么是Group Commit?

MySQL为了减少fsync次数,会把多个事务的binlog攒在一起写盘。比如同一时刻有事务A、B、C都在commit,MySQL会把它们放在一个组里,只做一次fsync。

时间线:
  t1: 事务A commit → 进入binlog cache
  t2: 事务B commit → 进入binlog cache  
  t3: 事务C commit → 进入binlog cache
  t4: 三个事务一起fsync到binlog文件(一次IO搞定)

组提交的关键:同一个组里的事务,在主库就是"同时"提交的,它们之间大概率不冲突。

5.7在binlog里记录了每个事务的"逻辑时钟"(其实就是组提交的序列号)。从库看到两个事务的逻辑时钟相同,就知道它们在主库是同一组提交的,可以并行重放。

# my.cnf
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
slave_preserve_commit_order = ON

比5.6好在哪?不看数据库了,看主库实际的提交关系。单库多表也能并行。

但也容易踩坑:slave_preserve_commit_order 这个参数一定要开。不开的话,从库事务的提交顺序可能跟主库不一致,某些依赖提交顺序的业务逻辑会出问题。开了会损失一些并行度,但数据安全更重要。

第三代:WRITESET(MySQL 5.7.22+)

LOGICAL_CLOCK的问题在哪?粒度还是太粗。同一个组里的事务数量有限,大部分时间从库的worker还是在等。

WRITESET换了思路:不看"主库什么时候提交",而是看"两个事务操作的数据有没有重叠"。

事务A写了行 {id=1, id=2}
事务B写了行 {id=3, id=4}
→ A和B的写集合(WRITESET)不相交 → 可以并行!

即使事务A和事务B在主库不是同一个组提交的,只要它们操作的数据不重叠,从库也可以并行执行。

# my.cnf
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
slave_preserve_commit_order = ON

并行度比LOGICAL_CLOCK高很多,但也带来了新问题,我们后面展开讲。

第四代:WRITESET_SESSION(MySQL 8.0.27+)

WRITESET的隐患:同一session里的两个事务,如果操作不同行,WRITESET会认为它们不冲突、可以并行。但实际上同一个session的事务是有先后顺序的,乱序可能出问题。

8.0.27加了WRITESET_SESSION:在WRITESET的基础上,保证同一session的事务串行执行,不同session的事务仍然可以按写集合判断并行。

# my.cnf  推荐配置(8.0.27+)
binlog_transaction_dependency_tracking = WRITESET_SESSION
transaction_write_set_extraction = XXHASH64
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
slave_preserve_commit_order = ON

8.0.27+的环境,直接用这个就对了。


三、版本选型对照表

MySQL版本 推荐方案 关键参数
5.6 Schema并行(聊胜于无) slave_parallel_type=DATABASE
5.7.2 ~ 5.7.21 LOGICAL_CLOCK slave_parallel_type=LOGICAL_CLOCK
5.7.22 ~ 8.0.26 WRITESET binlog_transaction_dependency_tracking=WRITESET
8.0.27+ WRITESET_SESSION binlog_transaction_dependency_tracking=WRITESET_SESSION

不管哪个版本,slave_preserve_commit_order 都建议开。


四、并行度怎么设?不是越大越好

slave_parallel_workers 设多少合适?我最初的想法是"越多越好",直接设了32。结果从库CPU飙到100%,锁争用严重,延迟反而更大了。

经验值:

机器配置 建议worker数
4核8G 4~8
8核16G 8~16
16核32G 16~32

调优步骤:

  1. 先设8,观察一周
  2. 看从库CPU使用率和延迟趋势
  3. CPU低于60%且延迟还在涨 → 加worker
  4. CPU高于80%或锁争用明显 → 减worker
-- 查看worker状态
SELECT 
  worker_id,
  thread_id,
  service_state,
  last_error_number,
  last_error_message
FROM performance_schema.replication_applier_status_by_worker;

五、延迟排查路径:四种现象四种打法

并行复制配好了,延迟还是高?下面四种场景,我一个个讲。

场景一:延迟持续增长,越来越大

十个延迟问题里有八个是这种。排查路径:

-- 第一步:看从库在干什么
SHOW PROCESSLIST;

-- 第二步:看有没有大事务阻塞
SELECT * FROM information_schema.innodb_trx
WHERE trx_started < NOW() - INTERVAL 30 SECOND
ORDER BY trx_started;

-- 第三步:看从库锁等待
SELECT * FROM performance_schema.data_lock_waits;

常见原因:

  • 主库有大事务(DELETE几百万行、ALTER大表)
  • 从库IO慢,relay log写入跟不上
  • 并行复制没开或者配错了

解法就是大事务拆小批量执行,IO慢就换SSD,并行复制参数按上面配。

场景二:延迟抖动,一阵一阵的

延迟不是持续增长,而是每隔几分钟飙一次,然后又慢慢降下来。

排查方向:

  • 定时任务(每5分钟的批量写入)
  • 监控/备份脚本(mysqldump、xtrabackup跑的时候IO飙升)
  • 主库的checkpoint

这种场景通常是业务层的写入不均匀导致的。

场景三:Seconds_Behind_Master=0,但业务读到老数据

这个最坑。监控看着一切正常,延迟为0,但业务就是查不到新数据。

-- 用performance_schema看真实延迟
SELECT * FROM performance_schema.replication_applier_status;

-- 或者用心跳表对比
-- 主库写入当前时间
UPDATE heartbeat SET ts = NOW(6) WHERE id = 1;
-- 从库读取对比
SELECT TIMESTAMPDIFF(SECOND, ts, NOW(6)) AS real_lag FROM heartbeat WHERE id = 1;

Seconds_Behind_Master是基于binlog里TIMESTAMP字段计算的,当某些事务不包含时间戳信息时,这个指标会显示0,但实际延迟可能存在。

解法就是上心跳表做业务级监控,比Seconds_Behind_Master靠谱得多。

场景四:延迟突然归零又涨上去

通常是某个大事务执行完了,SQL线程瞬间追上,然后下一个大事务又开始堆积。

看relay log空间能辅助判断:

SHOW SLAVE STATUS\G
-- 看 Relay_Log_Space 字段
-- 如果一直在涨,说明IO线程拉的比SQL线程放的快

六、业务层调优:光调数据库参数不够

并行复制能解决SQL线程串行的瓶颈,但有些问题光靠参数搞不定。

拆大事务

大事务是延迟的头号杀手。

-- 错误写法:一次删100万行
DELETE FROM logs WHERE created_at < '2024-01-01';

-- 正确写法:分批删除
DELETE FROM logs WHERE created_at < '2024-01-01' LIMIT 10000;
-- 应用层循环执行,每批之间sleep(1)

我之前接手的一个项目,开发同学写了个定时清理任务,每个月删3000万行日志,一个DELETE搞定。跑了两个月我才发现从库延迟经常飙到1小时。改成每批5000行、间隔0.5秒之后,延迟从没超过10秒。

大表DDL用工具

-- 错误写法:直接ALTER
ALTER TABLE big_table ADD COLUMN x INT;

-- 正确写法:用gh-ost或pt-online-schema-change
-- gh-ost不会长时间锁表,对复制友好

读写分离要注意"读后写"

开了读写分离之后,如果有业务逻辑是"先SELECT再UPDATE",SELECT走从库可能读到老数据,UPDATE走主库写了新数据,中间就出问题了。

要么在代码里强制这类查询走主库,要么用中间件(ProxySQL)配置规则把这类SQL路由到主库。


七、监控方案:光看一个指标不够

复制状态监控

-- 最基础:SHOW SLAVE STATUS
SHOW SLAVE STATUS\G
-- 关注:Slave_IO_Running, Slave_SQL_Running, Seconds_Behind_Master

-- 更准确:performance_schema
SELECT * FROM performance_schema.replication_connection_status\G
SELECT * FROM performance_schema.replication_applier_status\G

业务延迟监控

心跳表方案:

-- 在monitor库建心跳表
CREATE TABLE heartbeat (
  id INT PRIMARY KEY,
  ts TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
);
INSERT INTO heartbeat (id) VALUES (1);

后台任务每秒在主库UPDATE心跳表的ts字段,然后对比主从ts差值。这个差值就是真实延迟。比Seconds_Behind_Master准确,不依赖binlog里的时间戳。


八、升级5.7到8.0的并行复制变化

如果你的环境在做5.7→8.0升级,并行复制方面有几个点要注意:

变化 说明
WRITESET_SESSION 8.0.27+新增,比WRITESET更安全,推荐开启
命令语法更新 SHOW SLAVE STATUS → SHOW REPLICA STATUS(8.0.22+兼容老语法)
redo log格式变化 8.0 redo log格式有调整,升级后需重新调innodb_redo_log_capacity
认证插件变更 caching_sha2_password是8.0默认,从库连主库要确认密码插件兼容

升级后的推荐配置:

# 8.0.27+ 推荐
binlog_transaction_dependency_tracking = WRITESET_SESSION
transaction_write_set_extraction = XXHASH64
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8
slave_preserve_commit_order = ON
innodb_redo_log_capacity = 4294967296  # 4GB,单位字节

九、面试怎么答

面试官:MySQL并行复制经历了哪几个阶段?各自原理是什么?

并行复制经历了四个阶段。5.6是基于Schema的并行,不同数据库的事务可以同时执行,但单库场景没用。5.7.2引入LOGICAL_CLOCK,利用主库的Group Commit机制,同组提交的事务可以在从库并行重放。5.7.22进一步引入WRITESET,通过分析事务的写集合判断是否冲突,不冲突就并行,粒度更细。8.0.27加了WRITESET_SESSION,在WRITESET基础上保证同一session事务串行,避免乱序问题。生产里建议8.0.27+直接用WRITESET_SESSION。

面试官:slave_preserve_commit_order为什么建议开启?

并行重放后,从库事务的提交顺序可能跟主库不一致。如果业务逻辑依赖提交顺序(比如先插入后更新的时序),不开启可能导致数据不一致。开启后会损失一部分并行度,但换来的是数据安全性。生产环境建议始终开启。

面试官:Seconds_Behind_Master为0但业务读到老数据,怎么排查?

Seconds_Behind_Master基于binlog中的TIMESTAMP字段计算,当事务不包含时间戳时会显示0。先用performance_schema的replication_applier_status查真实状态,再上心跳表做业务级监控——主库每秒写入当前时间到心跳表,从库读取对比差值,这个差值就是真实延迟。


避坑清单

序号 坑点 后果 正确做法
1 不开并行复制就指望延迟自己降 延迟随写入量线性增长 5.7+至少用LOGICAL_CLOCK
2 slave_parallel_workers设太大 CPU飙高、锁争用严重 从8开始,观察后再调
3 不开slave_preserve_commit_order 从库提交顺序错乱,数据不一致 生产环境必须ON
4 WRITESET模式下同一session事务冲突 从库数据错乱或报错 8.0.27+用WRITESET_SESSION
5 只看Seconds_Behind_Master 指标失真以为没延迟 上心跳表+performance_schema
6 主库跑大事务不拆分 从库阻塞几十分钟 分批执行,每批LIMIT控制行数
7 大表直接ALTER不走工具 复制延迟飙升、锁表 用gh-ost或pt-osc
8 读写分离不考虑"读后写" 业务读到旧数据做错误写入 关键查询路由到主库
9 升级8.0后不调redo log参数 redo log格式变化导致IO异常 升级后调innodb_redo_log_capacity
10 以为调参数就能解决所有延迟 忽略业务层大事务、定时任务等根因 参数+业务双管齐下

总结

并行复制不是"改个参数就完事"的事。四代演进下来,每一代解决了不同层面的问题:

  • 5.6 Schema并行 → 解决"完全没有并行"
  • 5.7 LOGICAL_CLOCK → 利用组提交实现真正的并行
  • 5.7.22 WRITESET → 通过写集合分析,粒度更细
  • 8.0.27 WRITESET_SESSION → 兼顾并行度和session顺序

但并行复制只解决SQL线程的瓶颈。真正要做到延迟可控,还得在业务层把大事务拆掉、定时任务分批跑、读写分离路由做对。

并行复制解决"干活的人少"的问题,业务优化解决"活太大"的问题。两个都搞定,从库追不上主库的问题基本就治好了。

我是数据库小学妹,咱们下篇见 👋

相关文章
|
3月前
|
SQL 关系型数据库 MySQL
EXPLAIN 执行计划:一眼看穿你的SQL慢在哪
数据库小学妹带你轻松掌握SQL性能诊断!通过EXPLAIN查看执行计划,精准识别索引失效、全表扫描(ALL)、key为NULL等瓶颈。聚焦type、key、rows等6个关键字段,结合实战案例与避坑指南(如函数滥用、最左前缀破坏),让优化有的放矢。学完即用,告别盲目调优!
|
3月前
|
SQL 安全 关系型数据库
批量更新不用游标:CASE WHEN + 集合操作,一行SQL搞定!
数据库小学妹分享MySQL批量更新技巧:用`CASE WHEN`+集合操作或`JOIN`临时表,一条SQL高效更新多行,告别低效游标!兼顾性能、安全与可维护性,百万数据秒级完成。
|
28天前
|
SQL 中间件 关系型数据库
读写分离中间件怎么选?ProxySQL 落地踩坑与选型对比
ProxySQL是一款轻量高性能MySQL中间件,原生支持读写分离、自动故障切换与查询路由,相比MyCAT、ShardingSphere更专注、易用、低损耗,特别适合仅需主从读写分离的场景。
|
28天前
|
SQL 关系型数据库 MySQL
SQL代码审查指南:命名规范+10大反模式+四维检查清单,一篇全搞定
数据库小学妹带你攻克SQL规范难题!从命名、格式到10大反模式(如SELECT*、隐式转换、ORDER BY RAND等),结合真实踩坑案例,详解可读、可维护、高性能的SQL写法,并提供SQL Review四维审查清单与团队落地方法,助你写出工业级质量SQL。
|
2月前
|
SQL Java 中间件
读写分离与查询路由实战:从原理到Spring Boot代码实现
本文由“数据库小学妹”详解读写分离与查询路由实战:基于Spring Boot + 动态数据源(AbstractRoutingDataSource + AOP)实现主从库自动分流;对比ShardingSphere等中间件方案;涵盖强制读主、延迟感知、负载均衡等路由策略及避坑指南。
|
2月前
|
canal 缓存 NoSQL
数据库扛不住高并发?Redis缓存+双写一致性:给你的系统装上“涡轮增压”
数据库小学妹带你破解Redis缓存一致性难题!面对高并发,如何确保Redis与数据库数据同步?详解“先更库后删缓”“延时双删”“Binlog异步同步”等4大方案,直击雪崩、击穿、穿透三座大山,助你构建又快又稳的数据库架构.
|
2月前
|
消息中间件 NoSQL 数据库
分库分表后数据不一致?3种分布式事务方案,帮你彻底解决“钱货不等”难题
本文由“数据库小学妹”详解分布式事务核心难题:分库分表后如何保障跨库数据一致性。涵盖TCC、消息队列(最终一致性)、2PC等方案对比,强调互联网场景首选“MQ+幂等+本地消息表”,并指出避坑要点(重复消费、消息丢失、悬挂问题)。
|
2月前
|
消息中间件 关系型数据库 MySQL
CDC实时数据同步:让数据库变更秒级流向大数据平台!
本文由“数据库小学妹”生动讲解CDC(变更数据捕获)核心原理与实战:基于MySQL binlog实时捕获INSERT/UPDATE/DELETE事件,通过Debezium解析为含before/after的结构化消息,推送至Kafka,实现缓存、ES、Flink等系统的零侵入、秒级同步。兼顾原理、避坑与场景,让数据流通真正实时可靠。
|
2月前
|
SQL 缓存 关系型数据库
主从延迟的5大“元凶”+3个排查命令,别再让从库拖后腿
数据库小学妹详解MySQL主从延迟:5大元凶(硬件弱、写压大、慢查询、网络差、大事务)+3条核心排查命令(SHOW SLAVE STATUS等),助你快速定位、精准优化,避坑生产故障!
|
2月前
|
SQL 算法 中间件
如何让海量数据跑得更快?分库分表实战,从入门到避坑
本文深入解析MySQL分库分表核心原理与实战,结合ShardingSphere中间件,详解垂直/水平拆分策略、路由计算、SQL归并及分布式事务、全局ID、平滑扩容等避坑要点,助你突破单库瓶颈,构建高并发、海量数据下的高可用数据库架构。