面试考察点
- 能否描述主库写 Binlog、副本拉取并回放的基本过程。
- 是否理解复制默认通常是异步的,提交成功不等于副本已经应用。
- 能否区分复制延迟的网络、生成、传输和应用瓶颈。
- 是否知道读写分离会引入读己之写问题。
- 能否说明高可用不只是“有一个从库”,还需要探活、选主、防脑裂和数据校验。
复制的基本流程
主库提交事务并写入 Binlog。副本的接收线程连接主库,拉取事件并写入本地 Relay Log;应用线程读取 Relay Log,在副本执行相同的数据变更。
主库事务 → Binlog
↓
副本接收线程
↓
Relay Log
↓
副本应用线程
↓
副本数据
现代 MySQL 常使用基于 GTID 的复制。GTID 为每个已提交事务提供全局标识,有助于识别事务是否执行过以及简化故障切换后的复制拓扑重建。
异步、半同步与组复制
异步复制中,主库事务提交不等待副本确认,吞吐和可用性较好,但主库突然损坏时可能丢失尚未传输的事务。
半同步复制通常要求至少一个副本确认已收到相关日志后主库再返回成功,它降低已确认事务完全丢失的概率,但“收到”不一定等于“已应用”。副本或网络异常时可能退化,并增加提交延迟。
组复制通过成员协议、事务认证和多数派等机制提供更强的高可用基础,但仍需理解冲突限制、网络分区、路由层和运维复杂度,不能把它当作零成本强一致方案。
复制延迟如何产生
延迟可能来自:
- 主库短时间产生大量写入,副本应用速度跟不上。
- 单个超大事务需要很久才能传输和回放。
- 缺少索引导致副本执行更新或删除效率低。
- 副本承载大量慢查询,与复制线程争抢 CPU、IO 和锁。
- 网络抖动、磁盘延迟或副本硬件弱于主库。
- DDL、元数据锁或长事务阻塞应用线程。
Seconds_Behind_Source 只能作为一个信号,不能完整表达所有延迟。还应观察接收与执行位置差距、Relay Log 积压、应用线程状态和 GTID 集合。
并行复制
早期单线程回放无法利用多核。现代 MySQL 可以按逻辑时钟等依赖信息并行应用互不冲突的事务。并行度不是越大越好,受事务依赖、热点行、磁盘与 CPU 限制。
若业务所有写入都集中在同一热点记录,调高线程数也无法突破依赖约束。需要从分库分表、热点拆分和事务大小上解决。
读写分离的一致性问题
用户刚在主库完成写入,立即去副本查询时,副本可能尚未回放该事务,于是看不到自己的修改。这叫读己之写一致性问题。
常见方案包括:
- 写后一定时间内固定读主库。
- 将 GTID 或日志位点随上下文传递,等待副本追到对应位置再读。
- 对余额、库存状态等关键读始终访问主库。
- 业务界面采用提交结果直接渲染,但后台仍需正确处理后续读取。
固定等待若过短不能保证,过长又浪费延迟;关键链路应采用可验证的位置机制或直接读主库。
高可用切换的完整问题
仅有副本并不等于高可用。自动切换至少需要解决:
- 如何判断主库真的故障,而不是监控节点自身网络异常。
- 多个副本中哪个数据最完整、延迟最小。
- 如何确保旧主库不能继续接受写入,避免脑裂。
- 应用连接如何切换到新主库。
- 其他副本如何重新指向新主库。
- 故障窗口可能丢失多少事务,如何核对和补偿。
Fencing 是关键:通过断开旧主网络、撤销写权限、关闭实例或存储租约等手段确保同一时刻只有一个主库可写。
复制不能替代备份
误删、错误更新和逻辑损坏会被迅速复制到所有副本。副本解决机器故障和读扩展,备份与 Binlog 时间点恢复解决历史版本恢复。两者目标不同,必须同时建设。
线上延迟处理步骤
- 确认是接收延迟还是应用延迟。
- 查看副本线程状态、Relay Log 积压和当前执行事务。
- 检查主库是否刚执行大事务、批量 DDL 或流量突增。
- 检查副本磁盘、CPU、锁等待和慢查询。
- 评估暂停非关键查询、增加并行度或临时分流。
- 根治大事务、索引缺失、资源规格或写入模型问题。
不要在未确认数据完整性时跳过事务或强行重建复制,这可能把暂时延迟变成永久不一致。
核心考点清单
- 复制以 Binlog 为基础,副本通过 Relay Log 回放事务。
- 默认异步复制存在数据丢失窗口,半同步降低风险但增加提交延迟。
- 延迟要区分日志接收与事务应用,单个指标不足以诊断。
- 读写分离必须设计读己之写策略。
- 高可用切换需要选主、Fencing、路由和数据核对,副本不能替代备份。
高频追问与参考回答
追问 1:主库提交成功后副本一定有数据吗?
异步复制下不一定。主库只保证自身提交,Binlog 可能尚未发送或副本尚未应用。半同步也通常只保证至少一个副本收到日志,不等于查询立即可见。
追问 2:为什么复制延迟为 0 仍可能读不到?
秒级延迟指标精度有限,也可能在线程异常或时间基准变化时不可靠。应使用 GTID、执行位置或业务数据校验确认副本已应用目标事务。
追问 3:如何处理大事务导致的延迟?
根本方案是将业务写入拆成可控批次,缩短事务并避免一次修改海量数据。副本侧提高并行度对单个不可拆分事务帮助有限。
追问 4:读请求都发副本就能减轻主库压力吗?
会引入一致性、容量和故障切换复杂度。关键读仍可能需要主库,副本慢查询还会与复制线程竞争资源,必须做路由分级和容量治理。
追问 5:GTID 有什么价值?
它用全局事务标识记录执行集合,便于判断事务是否已执行、选择数据更完整的副本,以及在切换后自动定位复制位置。
机制全景图
下面把「MySQL 主从复制、延迟与高可用设计」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。
flowchart LR
A["主库提交事务"]
A --> B["生成并发送 binlog"]
B --> C["副本接收 relay log"]
C --> D["并行重放事务"]
D --> E["故障检测后切换流量"]
完整链路:从输入到结果
沿着「主库提交事务 → 生成并发送 binlog → 副本接收 relay log → 并行重放事务 → 故障检测后切换流量」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。
1. 主库提交事务
主库提交成功的持久化级别由 redo 与 binlog 刷盘参数决定,复制承诺从这里开始。
2. 生成并发送 binlog
dump 线程或协议把 binlog 发送给副本;异步、半同步决定主库等待到什么确认点。
3. 副本接收 relay log
副本 I/O 线程接收事件写 relay log,网络抖动会形成接收延迟。
4. 并行重放事务
SQL/applier 线程按依赖并行执行,热点大事务和 DDL 可能限制并行度。
5. 故障检测后切换流量
高可用系统判断主库故障、选择数据最新且健康的副本晋升,并通过 fencing 阻止旧主继续写。
源码与实现定位
| 入口 | 阅读重点 |
|---|---|
| performance_schema.replication_applier_status_by_worker | 并行回放与错误 |
| SHOW REPLICA STATUS | 接收/执行位点与 GTID |
源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。
参数配置与可复现实验
CHANGE REPLICATION SOURCE TO SOURCE_AUTO_POSITION = 1;
SET GLOBAL rpl_semi_sync_source_enabled = ON;
注入主库宕机、网络分区和大事务,记录副本 GTID 差、选主、fencing 与业务恢复时间。
验证步骤与预期结果
1. 固定输入和基线
先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「GTID 差」为主基线,记录值应满足「切换候选为 0」;同时保存 复制字节与应用位置差距、事务重放延迟,使后续变化能够回到同一时间轴比较。
2. 从实现入口确认路径
在「performance_schema.replication_applier_status_by_worker」确认请求确实进入「并行回放与错误」对应的实现,再沿「SHOW REPLICA STATUS」观察「接收/执行位点与 GTID」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。
3. 注入本文特有的失败模式
优先复现「只看 Seconds_Behind_Master 判断延迟」,并把单一变量逐级放大,直到「GTID 差」越过「存在未应用事务」。随后再分别验证「切换未 fencing 旧主」和「大事务让复制追赶时间超出 RTO」,三类故障分开执行,避免多个变量同时变化而无法归因。
4. 执行止损和根因修复
第一轮只应用「先 fencing 旧主」,确认它能控制影响范围;第二轮应用「按 GTID/健康选副本」,验证核心链路恢复;最后落实「切换后逐表/事件核对」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。
5. 通过退出条件
实验只有同时满足三项才算通过:「GTID 差」回到「切换候选为 0」、「RTO」回到「小于业务目标」、「applier lag」回到「稳态秒级内」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。
量化基线
| 指标 | 样例基线/口径 | 风险线 | 结论 |
|---|---|---|---|
| GTID 差 | 切换候选为 0 | 存在未应用事务 | 不晋升 |
| RTO | 小于业务目标 | 超过演练线 | 自动化不足 |
| applier lag | 稳态秒级内 | 持续增长 | 热点/大事务 |
这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。
事故复盘:网络分区导致双主写入
控制面无法访问旧主就直接晋升副本,但旧主仍服务部分客户端,形成两个写入源。增加租约或 STONITH fencing、单写入口和旧主只读校验后,切换才不会只解决可用性却破坏一致性。
| 失败模式 | 首要证据 | 第一处置动作 |
|---|---|---|
| 只看 Seconds_Behind_Master 判断延迟 | 复制字节与应用位置差距 | 先 fencing 旧主 |
| 切换未 fencing 旧主 | 事务重放延迟 | 按 GTID/健康选副本 |
| 大事务让复制追赶时间超出 RTO | 副本并行工作线程利用率 | 切换后逐表/事件核对 |
发布与回滚检查点
- 发布前:确认「performance_schema.replication_applier_status_by_worker」对应实现和上述配置在目标版本仍然有效,并保存「GTID 差」基线。
- 灰度中:同时观察 复制字节与应用位置差距、事务重放延迟、副本并行工作线程利用率;任一指标越过表中风险线,就停止继续扩量。
- 回滚时:先执行「先 fencing 旧主」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
- 发布后:至少覆盖一个完整峰值周期,确认「只看 Seconds_Behind_Master 判断延迟」没有再次出现,才关闭变更观察窗口。
方案对比与选型
| 方案 | 更适合的场景 | 主要收益 | 代价与边界 |
|---|---|---|---|
| 异步复制 | 允许少量 RPO 且延迟敏感 | 主库提交快 | 主故障可能丢未复制事务 |
| 半同步复制 | 希望降低数据丢失窗口 | 至少一个副本收到日志 | 副本或网络慢会影响提交 |
| 组复制/共识方案 | 自动成员管理与更强单主约束 | 故障转移集成度高 | 运维复杂、写延迟与限制更多 |
选型至少带上 数据规模、选择性、读写比、事务长度和峰值并发,并用上面的量化基线验证;未知数据应明确为待测假设。
设计边界与工程取舍
高可用必须明确 RPO、RTO 和故障模型;“有主从”不等于可切换,切换还需要选主、隔离旧主、路由刷新和数据核对。
工程落地遵循:正确性由约束和事务兜底,性能优化必须用执行计划与测量验证。回答时直接引用「performance_schema.replication_applier_status_by_worker」、配置实验和事故数据,比复述固定模板更有说服力。