先说结论
MySQL 复制靠 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、锁等待和慢查询。
- 评估暂停非关键查询、增加并行度或临时分流。
- 根治大事务、索引缺失、资源规格或写入模型问题。
不要在未确认数据完整性时跳过事务或强行重建复制,这可能把暂时延迟变成永久不一致。
常见问题
追问 1:主库提交成功后副本一定有数据吗?
异步复制下不一定。主库只保证自身提交,Binlog 可能尚未发送或副本尚未应用。半同步也通常只保证至少一个副本收到日志,不等于查询立即可见。
追问 2:为什么复制延迟为 0 仍可能读不到?
秒级延迟指标精度有限,也可能在线程异常或时间基准变化时不可靠。应使用 GTID、执行位置或业务数据校验确认副本已应用目标事务。
追问 3:如何处理大事务导致的延迟?
根本方案是将业务写入拆成可控批次,缩短事务并避免一次修改海量数据。副本侧提高并行度对单个不可拆分事务帮助有限。
追问 4:读请求都发副本就能减轻主库压力吗?
会引入一致性、容量和故障切换复杂度。关键读仍可能需要主库,副本慢查询还会与复制线程竞争资源,必须做路由分级和容量治理。
追问 5:GTID 有什么价值?
它用全局事务标识记录执行集合,便于判断事务是否已执行、选择数据更完整的副本,以及在切换后自动定位复制位置。