MySQL 主从复制的原理是什么?
简化版
MySQL 主从复制的核心是:主库把变更写入 binlog,从库拉取 binlog 写入中继日志,再由 SQL 线程或复制执行线程重放这些变更。它常用于读写分离、备份和容灾,但天然可能存在复制延迟。
详细版
典型复制流程:
- 主库提交事务并写入 binlog;
- 从库 I/O 线程连接主库,请求 binlog;
- 主库发送 binlog 事件给从库;
- 从库写入 relay log;
- 从库 SQL 线程或复制执行线程读取 relay log 并执行。
常见问题:
- 复制延迟:主库写入成功后,从库还没重放完成,读从库可能读到旧数据。
- 主从不一致:非确定性 SQL、手工改从库、复制中断等都可能导致。
- 故障切换复杂:主库故障后,需要保证新主库数据尽量完整,并处理应用连接切换。
复制可以是异步、半同步或组复制等模式。普通异步复制性能好,但不能保证从库实时拿到所有事务。
完整版教学
一、binlog 是复制的基础
主从复制不是把数据页直接拷贝过去,而是基于 binlog 传播变更。主库执行写操作后,把逻辑变更记录到 binlog。从库拿到这些事件后,在自己本地重放,从而让数据逐渐追上主库。
这也解释了为什么 binlog 格式、事务提交顺序、复制位点都很重要。复制同步的是“变更历史”,不是简单的文件镜像。
binlog 常见格式有 statement、row、mixed。现代生产里更常见 row 格式,因为它记录行级变更结果,对非确定性函数、触发器影响等情况更稳。比如 update account set balance = balance - 100 where id = 1,row 格式会记录具体哪行变成什么值,从库按事件重放,结果更可控。
| binlog 格式 | 记录内容 | 特点 |
|---|---|---|
| statement | 原始 SQL | 日志小,但非确定性 SQL 风险高 |
| row | 行变更前后信息 | 更可靠,日志可能更大 |
| mixed | 两者混合 | MySQL 根据场景选择 |
二、复制链路怎么跑起来
从库一般有两个关键动作:
- 拉取主库 binlog,写入本地 relay log;
- 读取 relay log,并在从库执行对应事件。
旧版本资料常说 I/O 线程和 SQL 线程。现代 MySQL 支持多线程复制,执行层可以并行重放部分事务,但原理仍然是拉取日志、保存日志、重放日志。
可以把复制链路拆成三段:主库产生 binlog,从库接收并写 relay log,从库执行 relay log。任何一段慢下来都会造成延迟。面试回答时用“拉取、落中继日志、重放”这三个动词,比只说“主库同步给从库”更准确。
Master:
事务提交 -> 写 binlog -> dump 线程发送事件
Replica:
I/O 接收线程 -> 写 relay log -> SQL/worker 线程重放
记忆钩子:主从复制不是从库直接读主库数据页,而是从库消费主库的 binlog 变更流。
三、为什么会有主从延迟
主从延迟的根源是:主库提交完成,不等于从库已经执行完成。延迟可能来自:
- 主库写入压力太大,binlog 产生速度超过从库重放速度;
- 从库机器性能较弱;
- 大事务执行时间长;
- SQL 单线程或并行度不足;
- 网络抖动;
- 从库上还有额外查询压力。
读写分离场景下,如果写完马上读从库,就可能读到旧数据。关键业务可以读主库,或者使用延迟检测、会话一致性策略。
用数字看:主库每秒产生 5000 个事务,从库每秒只能重放 3500 个事务,每秒就积压 1500 个事务;持续 60 秒就积压 9 万个事务。即使网络没有问题,从库也会越来越落后。大事务尤其明显,一个事务更新 100 万行,主库提交后从库必须重放很久,期间延迟可能陡增。
业务上最典型的坑是“写后读”。用户刚修改昵称,应用马上把下一次读取路由到从库,如果从库还没重放这条 binlog,页面就显示旧昵称。解决方式可以是写后短时间读主库、按 GTID/位点等待从库追上、或对关键接口禁用读从库。
四、异步复制和半同步复制的区别
异步复制下,主库事务提交不需要等待从库确认。它吞吐好、延迟低,但主库刚提交就宕机时,从库可能还没收到这部分 binlog。
半同步复制会要求至少一个从库确认收到事务相关日志后,主库再向客户端返回成功。它能降低数据丢失风险,但会增加提交延迟。注意,半同步通常强调“收到日志”,不等于从库已经执行完查询可见。
对比可以这样说:
| 模式 | 主库提交是否等从库 | 优点 | 风险 |
|---|---|---|---|
| 异步复制 | 不等 | 性能好、延迟低 | 主库故障可能丢最后一段已提交事务 |
| 半同步复制 | 至少等一个确认收到日志 | 降低数据丢失风险 | 提交链路变长,仍可能有查询延迟 |
| 组复制 | 多节点协议保证更强一致性 | 高可用能力更强 | 架构和运维复杂度更高 |
半同步里“确认收到”不等于“已经执行”。所以它主要降低主库宕机导致事务丢失的风险,不直接保证读从库一定读到最新值。
五、如何降低复制风险
常见做法包括:
- 避免大事务,把批量写拆小;
- 从库配置合理并行复制;
- 监控复制延迟和复制错误;
- 读写分离时关键读走主库;
- 从库只读,避免人为写入造成不一致;
- 使用 GTID 简化故障切换和位点管理。
还要避免在从库执行业务写入。主从复制默认假设从库通过重放主库 binlog 变更数据,如果人为在从库改数据,就可能造成主从不一致甚至复制中断。生产中通常设置 read_only 或 super_read_only,并限制普通账号写权限。
六、故障切换为什么不只是改连接地址
主库宕机后,选择哪个从库提升为新主库,要看它是否拥有最完整的数据。如果从库 A 延迟 1 秒,从库 B 延迟 20 秒,把 B 提升为主库可能丢更多事务。使用 GTID 后,可以更方便地判断事务集合和补齐缺失事务,但仍需要成熟的切换流程。
故障切换还涉及应用连接、旧主库恢复后的角色、复制拓扑重建、双写风险防护。面试回答主从复制时,提到“复制不等于强一致高可用,切主需要处理延迟和数据完整性”,会比只背线程模型更工程化。
七、常见误区与追问
- 误区:主从复制是把主库数据文件直接复制给从库。 MySQL 复制核心是 binlog 事件传播和从库重放,不是简单拷贝数据页。
- 误区:半同步复制能保证从库马上可读到最新数据。 半同步通常保证至少一个从库收到日志确认,不等于从库已经执行完成。
- 误区:读写分离后所有读都可以走从库。 写后立刻读、支付状态、权限变更等关键读应考虑读主库或等待从库追上。
- 误区:主从延迟只由网络导致。 大事务、从库性能、并行复制不足、从库查询压力和主库写入峰值都可能导致延迟。
- 追问:GTID 的价值是什么? GTID 给每个事务全局标识,能简化复制位点管理、主从切换和判断事务是否已经执行。
- 追问:如何降低主从不一致风险? 使用 row 格式、从库只读、监控复制错误和延迟、避免非确定性写法,并建立规范的故障切换流程。
八、加强记忆
MySQL 主从复制的主线是:主库写 binlog,从库拉取并写 relay log,再由执行线程重放变更。异步复制性能好但可能丢最后一段事务,半同步降低丢失风险但增加提交等待,而且不代表从库已经执行可见。读写分离最容易踩写后读旧数据的坑,所以关键读要读主、等待位点或做会话一致性;故障切换也要先确认哪个从库数据最完整。