面试知识库
进阶

主从复制原理#

一句话答案#

主库写 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.7LOGICAL_CLOCK(基于组提交)同一组提交的事务在主库已经通过了锁检查、无锁冲突,所以从库可以并行回放依赖主库的组提交分组,压力小时分组小、并行度低
8.0WRITESET(基于行的写集合)计算每个事务修改的每一行的 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)会自动退化为异步

易错点:

  • ❌ 从库数据和主库完全一致——有延迟
  • ❌ 主从复制是同步的——默认异步