MySQL 主从复制:三个线程,一条链路

「三个线程」这句话谁都能背,但真正说不清的是:数据到底在哪一步才落到从库的表里。

交互版可以播放,光点会沿着真实链路走(异步 / 半同步两套):

▶ 打开交互版动图

建议在电脑上打开。图里每个模块都能点开看参数和坑。

先记住一句话:传的是日志,不是数据

主从复制不是把主库的数据页拷过去,而是:

主库把「改了什么」写进 binlog,从库拿到这些日志,在自己的库上重新执行一遍。

这个区别决定了后面所有事情:可以跨版本、跨硬件、跨操作系统,甚至主库 InnoDB、从库换个引擎(只要目标引擎支持这个操作)。因为传的是逻辑变更,不是物理页。

三个线程,各管一段

线程 在哪 干什么
Binlog Dump 主库 每个从库一个,读 binlog 往外推
I/O Thread 从库 连主库拉日志,写进 relay log
SQL Thread 从库 读 relay log,在从库上重放

关键在于:接收和执行是分离的,中间隔着一个 relay log。

所有复制延迟,本质上都是这个「分离」的代价——接收快、回放慢,日志就在 relay log 里堆着。

数据在哪一步才真正落到从库的表里

这是最容易记混的地方,也是面试常考点:

  • I/O 线程写完 relay log → 数据还没进从库的表
  • SQL 线程重放完成 → 数据才真正落地

所以 SHOW REPLICA STATUS 里看到 Slave_IO_Running: Yes,只能说明「连得上主库、收得到日志」。它不代表数据已经追平。

两个都要 Yes,才算链路健康:

SHOW REPLICA STATUS\G
-- Slave_IO_Running:  Yes   收得动
-- Slave_SQL_Running: Yes   放得动
-- Seconds_Behind_Source: 0  追平

复制延迟到底出在哪

按链路顺序,只有三处:

  1. 网络传输 —— 跨机房、跨地域,带宽和 RTT
  2. relay log 落盘 —— 从库磁盘 IO 打满(看 Relay_Log_Space 是否堆积)
  3. SQL 线程回放 —— 绝大多数情况下的真正瓶颈

第 3 条是结构性问题:主库 16 核并发写,从库一个线程串行追,天生追不上。

解法是并行复制(MTS):

  • LOGICAL_CLOCK:同一组提交(group commit)里的事务可以并行回放,因为它们本来就不冲突
  • WRITESET:按行计算冲突,不冲突就能并行,粒度更细,5.7.22+ 更推荐
replica_parallel_workers = 8
replica_parallel_type = LOGICAL_CLOCK

注意:并行复制只解决回放瓶颈。如果延迟来自网络或 relay log 落盘,调它没用。

半同步到底保证了什么

很多人以为开了半同步就「数据不丢、延迟没了」,其实它只保证一件事:日志不丢。

流程差异只有一处——主库写完 binlog 后停下来等,等至少一个从库回 ACK 说「日志我收到了」,才提交并把结果返回客户端。

要分清两个模式:

  • AFTER_SYNC(5.7+ 默认):主库先不提交,等 ACK 到了再提交 → 主库宕机不会出现「主库有、从库没有」,这叫 lossless
  • AFTER_COMMIT(5.6 老行为):主库先提交再等 ACK → 主库恰好在这时挂了,客户端已经收到成功,但从库可能没有 → 会丢

还有个隐蔽的坑:rpl_semi_sync_master_timeout 默认 10 秒,超时会自动降级为异步。之后 Rpl_semi_sync_master_status 会变成 OFF,但参数看着还是开着的。

所以监控不能只看参数,要看状态变量。

而且要强调:半同步不解决复制延迟。ACK 只确认 relay log 写到,SQL 线程的重放仍然是异步的。

为什么现在都用 GTID

传统复制靠 binlog 文件名 + 偏移量(比如 mysql-bin.000003:1547)。问题是:主库一切换,位点就变了,得重新算——算错就是数据不一致。

GTID 给每个事务一个全局唯一编号:server_uuid:事务号。从库不再说「我要从第 1547 字节开始」,而是说「我手上已经有这些 GTID 了」,主库自动发缺的那部分。

-- 看从库有没有追平
SELECT @@gtid_executed;
-- 主从切换后,让从库等到追上某个 GTID 再放流量
SELECT WAIT_FOR_EXECUTED_GTID_SET('xxxx-...:1-1000', 10);

最后那条在建表后立刻读、或者做读写分离切换时特别有用——它是「延迟读」问题的正解。


一句话收尾:主库发日志,从库重放日志,中间隔着一个 relay log。延迟不是 bug,是这个结构本身的性质;能不能接受,取决于你的业务允不允许读到旧数据。