阅读时间:约 45 分钟 | 难度:中高级 | 编号:125
一、引言:主从复制是高可用的基石
MySQL 主从复制是最基础也是最重要的高可用方案。几乎所有的高可用架构——读写分离、数据备份、故障切换——都建立在主从复制之上。
主从复制的核心: 主库将变更写入 binlog,从库通过 I/O 线程拉取 binlog 写入 relay log,SQL 线程重放 relay log 中的事件。

二、主从复制架构
2.1 三个线程
| 线程 | 位置 | 职责 |
|---|---|---|
| Binlog Dump 线程 | 主库 | 发送 binlog 给从库 |
| I/O 线程 | 从库 | 拉取 binlog 写入 relay log |
| SQL 线程 | 从库 | 读取 relay log 重放事件 |
2.2 复制流程
主库: 事务提交 → 写 binlog → Binlog Dump 线程发送
从库: I/O 线程接收 → 写 relay log → SQL 线程重放
2.3 复制模式
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 异步复制 | 主库提交后不等待从库 | 默认,性能最好 |
| 半同步复制 | 主库等待至少一个从库确认收到 binlog | 减少数据丢失 |
| 全同步复制 | 主库等待所有从库应用完成 | 极少使用 |

三、binlog 与 relay log
3.1 binlog
- 主库产生,记录所有数据变更
- 格式:STATEMENT / ROW / MIXED
- 文件:
binlog.000001,binlog.000002...
3.2 relay log
- 从库产生,I/O 线程从主库接收 binlog 后写入
- 格式与 binlog 相同
- 文件:
relay-log.000001,relay-log.000002... - SQL 线程重放后自动删除
3.3 关键位置
| 概念 | 说明 |
|---|---|
| Master_Log_File + Master_Log_Pos | 主库 binlog 位置(I/O 线程读取到哪) |
| Relay_Master_Log_File + Exec_Master_Log_Pos | SQL 线程执行到主库 binlog 的哪个位置 |

四、复制格式(ROW vs STATEMENT)
4.1 STATEMENT 格式
- 记录 SQL 语句
- 日志小,网络传输快
- 缺点:不确定函数(NOW(), UUID())导致主从不一致
4.2 ROW 格式(默认)
- 记录每行数据的变化
- 精确,保证主从一致
- 缺点:日志大,网络传输慢
4.3 MIXED 格式
- 默认 STATEMENT,需要时自动切换为 ROW

五、并行复制
5.1 为什么需要并行复制
传统复制只有一个 SQL 线程,主库多线程并发写入,从库单线程串行回放,延迟不可避免。
并行复制: 多个 SQL 线程同时回放 relay log 中的事务。
5.2 MySQL 5.6:基于 Schema 的并行复制
按数据库(Schema)并行。不同数据库的事务可以并行回放,同一个数据库的事务串行。
5.3 MySQL 5.7:基于 Group Commit 的并行复制(LOGICAL_CLOCK)
主库上同一组提交的事务在从库上可以并行回放。基于 binlog 的 last_committed 和 sequence_number。
5.4 MySQL 8.0:基于 Write Set 的并行复制
主库记录事务修改的行,如果两个事务修改的行没有冲突,可以在从库并行回放。比 LOGICAL_CLOCK 更细粒度。
-- 查看并行复制配置
SHOW VARIABLES LIKE 'slave_parallel_workers';
SHOW VARIABLES LIKE 'binlog_transaction_dependency_tracking';

六、复制延迟
6.1 延迟的原因
| 原因 | 说明 |
|---|---|
| 主库并发高 | 从库单线程回放慢 |
| 大事务 | 从库回放大事务耗时 |
| 网络延迟 | 跨机房复制 |
| 从库性能差 | 从库硬件不如主库 |
6.2 监控延迟
-- 查看从库延迟(秒)
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 0 ← 延迟秒数
-- 查看并行复制状态
SELECT * FROM performance_schema.replication_applier_status_by_worker;
6.3 减少延迟
- 开启并行复制(
slave_parallel_workers > 0) - 使用 ROW 格式 + WRITESET 依赖跟踪
- 拆分大事务
- 从库硬件不低于主库

七、GTID 复制
7.1 什么是 GTID
GTID(Global Transaction Identifier)是事务的全局唯一标识。
GTID = server_uuid:transaction_id
例如: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100
7.2 GTID 的优势
- 简化主从切换:不需要手动指定 binlog 位置
- 自动跳过已执行的事务
- 便于故障恢复
7.3 配置 GTID
-- 主库和从库都需要配置
gtid_mode = ON
enforce_gtid_consistency = ON

八、总结
| 组件 | 作用 |
|---|---|
| binlog | 主库变更日志 |
| relay log | 从库接收的 binlog 副本 |
| I/O 线程 | 从库拉取 binlog |
| SQL 线程 | 从库回放 relay log |
| 并行复制 | 多线程回放,减少延迟 |
| GTID | 全局事务标识,简化切换 |

九、面试官视角提问
Q1:主库突然宕机,从库提升为主库。如何确保没有数据丢失?半同步复制能保证零丢失吗?
Q2:**Seconds_Behind_Master**为 0,但主从数据不一致。可能的原因是什么?如何排查?
Q3:MySQL 5.7 的 LOGICAL_CLOCK 和 MySQL 8.0 的 WRITESET 并行复制有什么区别?哪种更高效?
Q4:从库的 SQL 线程停止了,但 I/O 线程正常运行。从库的 relay log 会无限增长吗?如何恢复?
Q5:一个表没有主键,对主从复制有什么影响?为什么 ROW 格式下没有主键的表会导致从库延迟?
Q6:GTID 复制下,如果从库手动插入了一条数据(不应该这么做),然后主库也插入了相同的数据,会发生什么?
作者注:本文为系列第 125 篇,属于「08 MySQL 与关系型数据库」分类。
十、面试官提问 · 参考答案
Q1:主库宕机,从库提升为主库。如何确保零数据丢失?半同步能保证吗?
参考答案:
半同步复制不能保证零丢失,只能减少丢失窗口。
半同步复制: 主库等待至少一个从库确认收到 binlog 后才提交。但如果主库在等待确认时宕机,已提交但未确认的事务在主库上已提交,从库可能没有。
零丢失方案: Group Replication(MGR)或 InnoDB Cluster。MGR 使用 Paxos 协议,多数派确认后才提交,可以保证零丢失。
增强半同步:rpl_semi_sync_master_wait_for_slave_count=1 且 rpl_semi_sync_master_wait_point=AFTER_SYNC 可以最大程度减少丢失。
Q2: 但主从数据不一致,可能原因?
参考答案:
可能原因:
- 从库直接写入:有人在从库上手动执行了 INSERT/UPDATE/DELETE
- STATEMENT 格式 binlog:不确定函数(NOW(), UUID())导致主从计算结果不同
- 主库使用了
**sql_log_bin=0**:某些操作不记录 binlog,从库无法同步 - 从库复制过滤:
replicate_ignore_table等过滤规则跳过了某些表 - Bug:MySQL 版本的复制 bug
排查: 使用 pt-table-checksum 检查主从数据一致性,pt-table-sync 修复不一致。
Q3:MySQL 5.7 LOGICAL_CLOCK 和 8.0 WRITESET 并行复制的区别?
参考答案:
| 维度 | 5.7 LOGICAL_CLOCK | 8.0 WRITESET |
|---|---|---|
| 并行粒度 | 同一组提交的事务 | 不冲突的事务 |
| 依赖检测 | 基于 group commit | 基于 Write Set(修改的行) |
| 并行度 | 较低(依赖主库提交顺序) | 较高(不冲突即可并行) |
| 性能 | 好 | 更好 |
WRITESET 更高效: 因为两个事务即使在不同组提交,只要修改的行不冲突,就可以并行回放。
Q4:从库 SQL 线程停止,I/O 线程正常。relay log 会无限增长吗?
参考答案:
会无限增长,直到磁盘满。 I/O 线程继续从主库拉取 binlog 写入 relay log,但 SQL 线程停止不会清理已执行完的 relay log。
恢复步骤:
- 查看 SQL 线程停止原因:
SHOW SLAVE STATUS\G看Last_SQL_Error - 修复错误(如主键冲突、表不存在)
- 重启 SQL 线程:
START SLAVE SQL_THREAD - 或跳过错误:
SET GLOBAL sql_slave_skip_counter = 1; START SLAVE;
Q5:没有主键的表对主从复制有什么影响?
参考答案:
ROW 格式下,没有主键的表会导致从库严重延迟。
原因: ROW 格式下,UPDATE/DELETE 通过主键定位行。没有主键时,InnoDB 使用隐藏的 DB_ROW_ID 定位,但需要全表扫描来找到匹配的行。主库上 1ms 的 UPDATE,从库上可能需要 1 秒。
最佳实践: 所有表必须有显式主键。DBA 应该定期检查并告警无主键表。
Q6:GTID 下从库手动插入数据,然后主库也插入相同数据,会发生什么?
参考答案:
从库的复制会停止,报错 GTID 冲突或主键冲突。
场景一: 从库插入 id=100,主库也插入 id=100。主库 binlog 传到从库,从库重放时发现主键冲突,SQL 线程停止。
场景二: 从库插入的记录主键不冲突,但从库的 GTID 不是主库产生的。如果主库后来执行了相同 GTID 的事务,从库会认为该 GTID 已执行而跳过。
最佳实践: 从库设置为 read_only=ON(除了复制线程),防止误操作。
全文完 | 编号 125 | 分类 08 MySQL 与关系型数据库