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 追平复制延迟到底出在哪
按链路顺序,只有三处:
- 网络传输 —— 跨机房、跨地域,带宽和 RTT
- relay log 落盘 —— 从库磁盘 IO 打满(看
Relay_Log_Space是否堆积) - 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 到了再提交 → 主库宕机不会出现「主库有、从库没有」,这叫 losslessAFTER_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,是这个结构本身的性质;能不能接受,取决于你的业务允不允许读到旧数据。