高 进阶
主从复制原理#
一句话答案#
主库写 binlog→从库 IO 线程拉取写 relay log→SQL 线程重放,支持异步/半同步/组复制。
核心要点
三个线程:
- 主库 Binlog Dump Thread:发送 binlog
- 从库 IO Thread:接收写入 relay log
- 从库 SQL Thread:重放 relay log
同步方式: 异步(默认) / 半同步(至少一个从库确认) / 组复制(Paxos)
主从延迟的根因(最高频深挖):
不对称:主库是多线程并发写(成百上千个连接同时提交事务),而从库 SQL 线程单线程串行回放 relay log,回放速度天然跟不上写入速度,积压就表现为延迟。
具体放大延迟的因素:
- 大事务:一个事务在主库执行 10 分钟,从库也得等它整体回放完才算这条 binlog 处理完,期间延迟飙升
- DDL / 大批量 UPDATE:长时间占用 SQL 线程
- 从库还要承担读流量:查询与回放争抢资源
- 延迟监控:
Seconds_Behind_Master不精确(主库空闲时会误显示 0),生产用pt-heartbeat在主库定时写入时间戳、从库读出来做差值,才是真实延迟
并行复制演进(解决单线程回放瓶颈的核心手段):
核心矛盾:从库要并行回放就必须保证并行的事务之间没有冲突(否则回放结果和主库不一致)。难点在于”如何判断哪些事务可以安全并行”,演进就是判断粒度越来越细:
| 版本 | 方案 | 并行依据 | 局限 |
|---|---|---|---|
| 5.6 | 按库(schema)并行 | 不同库的事务互不影响,可并行 | 大多数业务只有一个核心库 → 等于没并行 |
| 5.7 | LOGICAL_CLOCK(基于组提交) | 同一组提交的事务在主库已经通过了锁检查、无锁冲突,所以从库可以并行回放 | 依赖主库的组提交分组,压力小时分组小、并行度低 |
| 8.0 | WRITESET(基于行的写集合) | 计算每个事务修改的每一行的 hash,两个事务的 writeset 没有交集 = 无行冲突 → 可并行,与是否同组无关 | hash 计算有 CPU 开销 |
- 5.7 LOGICAL_CLOCK 的精髓:主库一组提交(同一 flush 阶段一起 fsync)的事务,意味着它们在 prepare 阶段同时持有锁且互不冲突(否则会互相阻塞、不可能进同一组),从库照这个”逻辑时钟”分组就能安全并行。开启:
slave_parallel_type=LOGICAL_CLOCK+slave_parallel_workers=N - 8.0 WRITESET 的精髓:不再依赖”同组”,而是直接按行 hash 判冲突,并行度显著更高,对低并发主库尤其有效(
binlog_transaction_dependency_tracking=WRITESET) - 保序:并行回放仍需保证同一行的修改有序、提交顺序一致,由
slave_preserve_commit_order=ON保证最终提交顺序与主库一致
半同步复制的关键细节(易被深挖打穿):
- 从库确认的是”收到并写入 relay log”,不是”回放完成”:所以半同步只防丢数据(保证至少一个从库有 binlog),不解决主从延迟、不保证立刻能从从库读到新数据——刚提交就去从库读仍可能读到旧值
- 两种 ACK 时机(
rpl_semi_sync_master_wait_point):- AFTER_SYNC(5.7+ 默认,更安全):主库 binlog 刷盘后、引擎提交前就等从库 ACK。主库 crash 时未提交事务还没对外可见,故障切换无”幻读已提交又丢失”问题
- AFTER_COMMIT(5.5 旧行为):主库引擎提交后才等 ACK。此时其他会话已能读到该事务,若主库随即 crash 且从库没收到,会出现”读到过又丢了”的幽灵数据
- 超时退化为异步:从库 ACK 超过
rpl_semi_sync_master_timeout(默认 10s)未返回,主库自动降级为异步复制继续对外服务,等从库恢复再切回半同步——所以半同步在网络抖动下并非”绝对不丢”
面试回答(2分钟版)
MySQL 主从复制依靠三个线程协作完成。主库的 Binlog Dump 线程负责把 binlog 事件发送给从库;从库的 IO 线程接收后写入本地的 relay log;然后从库的 SQL 线程读取 relay log 并重放,最终实现数据同步。同步模式有三种:默认的异步复制性能最好但主库宕机时可能丢失未同步的数据;半同步复制要求至少一个从库确认收到 binlog 才返回客户端,牺牲一点延迟换数据安全;组复制基于 Paxos 协议实现多主写入。binlog 格式方面,生产环境一般选 ROW 格式,虽然日志量更大但能精确记录行变更,避免 STATEMENT 格式在主从执行结果不一致的问题。主从延迟的监控主要看 Seconds_Behind_Master,优化手段包括从库开启并行复制、减少大事务、增加从库配置等。
追问与易错
追问方向:
- “主从延迟怎么监控和减少?”→ 监控用 Seconds_Behind_Master + pt-heartbeat 更精确;减少延迟:拆大事务、DDL 用 gh-ost、从库开并行复制(slave_parallel_type=LOGICAL_CLOCK)、提升从库硬件配置
- “binlog 格式选 ROW 还是 STATEMENT?”→ 生产环境推荐 ROW,精确记录行变更前后值,避免 STATEMENT 格式因执行环境差异导致主从数据不一致;缺点是日志量更大,可配合 binlog_row_image=minimal 优化
- “主库崩溃切从库可能丢数据吗?”→ 异步复制下可能丢,主库 binlog 尚未同步到从库的事务会丢失;半同步复制至少保证一个从库收到 binlog 后才返回成功,大幅降低丢失风险但不能完全杜绝
- “为什么从库单线程回放会延迟,怎么优化?”→ 主库多线程并发写、从库 SQL 线程单线程串行回放,速度跟不上就积压。优化靠并行复制:5.6 按库并行(实际意义小)、5.7 LOGICAL_CLOCK 基于组提交(同组事务在主库已无锁冲突故可并行)、8.0 WRITESET 按行 hash 判冲突并行度最高;配合 slave_preserve_commit_order 保证提交顺序
- “半同步能保证主从数据强一致、读到最新吗?”→ 不能。从库 ACK 的只是”收到 relay log”而非”回放完成”,所以半同步只防丢数据,不解决延迟,刚提交去从库读仍可能读到旧值
- “半同步 after_sync 和 after_commit 区别?”→ after_sync(5.7+ 默认)是 binlog 刷盘后、引擎提交前等 ACK,更安全;after_commit 是提交后才等 ACK,期间其他会话已能读到,主库 crash 且从库没收到会出现”读到过又丢失”的幽灵数据。另外从库 ACK 超时(rpl_semi_sync_master_timeout,默认 10s)会自动退化为异步
易错点:
- ❌ 从库数据和主库完全一致——有延迟
- ❌ 主从复制是同步的——默认异步