主从复制与读写分离
问题
MySQL 主从复制的原理是什么?有哪些复制模式?主从延迟怎么处理?读写分离如何实现?
答案
一、主从复制原理
核心流程(三个线程)
| 线程 | 位置 | 职责 |
|---|---|---|
| binlog dump | 主库 | 读取 binlog 并发送给从库 |
| IO thread | 从库 | 接收 binlog,写入 relay log |
| SQL thread | 从库 | 重放 relay log 中的事件 |
二、复制模式
1. 异步复制(默认)
主库提交 → 写 binlog → 返回客户端 → 异步发给从库
- 优点:性能最好,主库不等待从库
- 缺点:主库宕机时,从库可能丢失已提交的事务
2. 半同步复制(Semi-Sync)
主库提交 → 写 binlog → 等待至少一个从库确认收到 → 返回客户端
- 优点:至少一个从库有完整数据,数据更安全
- 缺点:性能有一定下降(等待从库确认的延迟)
| 参数 | 说明 |
|---|---|
rpl_semi_sync_master_wait_point=AFTER_SYNC | 增强半同步(5.7+),等 binlog sync 后再等从库确认 |
rpl_semi_sync_master_timeout=1000 | 超时退化为异步的毫秒数 |
AFTER_SYNC vs AFTER_COMMIT
- AFTER_COMMIT(旧版):主库提交后等从库确认,宕机可能出现幻读
- AFTER_SYNC(5.7+ 增强):主库 sync binlog 后等从库确认再提交,不会出现幻读
3. 全同步复制
所有从库都确认后才返回。性能最差,实践中很少使用。
4. GTID 复制(推荐)
GTID(Global Transaction Identifier)= server_uuid:transaction_id
GTID = 3E11FA47-71CA-11E1-9E33-C80AA9429562:23
| 传统复制 | GTID 复制 |
|---|---|
| 基于 binlog 文件名 + 位置 | 基于全局事务 ID |
| 搭建/切换需要手动定位 | 自动找点,简化运维 |
手动指定 MASTER_LOG_FILE/POS | 自动定位 MASTER_AUTO_POSITION=1 |
GTID 复制的优势:
- 主从切换(故障转移)时自动找到正确的复制位置
- 可以确认从库是否已执行某个事务
三、并行复制
从库的 SQL 线程是单线程重放 relay log 的,这严重限制了从库的重放速度,导致主从延迟。
| 版本 | 并行策略 | 粒度 |
|---|---|---|
| 5.6 | 按 schema(数据库)并行 | 不同库的事务可以并行 |
| 5.7 | 基于组提交的并行(LOGICAL_CLOCK) | 同一组提交的事务可并行 |
| 8.0 | 基于 writeset 的并行 | 不冲突的事务可并行,效果最好 |
-- MySQL 5.7+ 配置并行复制
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8; -- 并行线程数
四、主从延迟
延迟原因
| 原因 | 说明 |
|---|---|
| 从库机器性能差 | IO/CPU 不足以跟上主库 |
| 从库承担查询压力 | 读请求占用从库资源 |
| 大事务 | 一个大事务在从库重放耗时很长 |
| DDL | ALTER TABLE 等操作阻塞从库 |
| 单线程重放 | 5.6 以前只有一个 SQL 线程 |
| 网络延迟 | 跨机房部署 |
延迟监控
SHOW SLAVE STATUS\G
-- 关键字段:
-- Seconds_Behind_Master: 延迟秒数
-- Slave_IO_Running: IO 线程是否运行
-- Slave_SQL_Running: SQL 线程是否运行
延迟解决方案
| 方案 | 做法 |
|---|---|
| 并行复制 | 开启多线程重放 |
| 强制走主库 | 关键读操作直接查主库 |
| 半同步复制 | 保证至少一个从库不落后太多 |
| 避免大事务 | 大事务拆分为小事务 |
| 从库性能 | 保证从库机器配置不低于主库 |
五、读写分离
实现方式
| 方式 | 代表 | 优点 | 缺点 |
|---|---|---|---|
| 代码层 | Spring 主从数据源 | 灵活,可控 | 业务侵入 |
| 中间件 | ShardingSphere、ProxySQL | 对应用透明 | 多一层组件 |
| DNS/VIP | 读写分离 DNS | 简单 | 不够灵活 |
中间件方案示例
应用 → ProxySQL/ShardingSphere → 写请求 → 主库
→ 读请求 → 从库1 / 从库2(负载均衡)
读写分离的主从延迟问题
写完主库后立即读从库,可能读不到最新数据:
| 方案 | 做法 | 适用场景 |
|---|---|---|
| 强制走主 | 关键业务写后立即读主库 | 对一致性要求高的操作 |
| 延迟检查 | 查从库前检查延迟,超过阈值走主库 | 通用场景 |
| 等待复制 | 写后等待从库同步完成再返回 | 半同步复制 |
| 会话一致性 | 同一会话的写后读走主库 | 用户级一致性 |
实践建议
- 不要追求「所有读都走从库」
- 写后读走主库 是最简单实用的策略
- 统计报表、数据分析等非实时查询适合走从库
六、高可用架构
MHA(Master High Availability)
主库宕机时,MHA 自动选择数据最新的从库提升为新主库。
主流高可用方案对比
| 方案 | 切换方式 | 数据安全 | 复杂度 |
|---|---|---|---|
| MHA | 自动主从切换 | 尽量补齐差异日志 | 中 |
| MGR | 组复制,Paxos 协议 | 强一致 | 高 |
| Orchestrator | 自动发现、拓扑管理 | 依赖复制模式 | 中 |
| InnoDB Cluster | MGR + Router + Shell | 强一致,官方方案 | 高 |
常见面试问题
Q1: 主库宕机了怎么办?
答案:
- 检测宕机:VIP 检测/MHA 心跳检测
- 选择新主:选择数据最新的从库(
Relay_Log_File最大的) - 补齐日志:其他从库向新主库同步差异日志
- 切换流量:VIP 漂移或 DNS 切换到新主库
- 修复旧主:旧主恢复后作为从库挂载到新主
Q2: GTID 复制的限制有哪些?
答案:
- 不支持
CREATE TABLE ... SELECT语句 - 不支持在同一事务中同时操作 InnoDB 表和 MyISAM 表
- 不支持
CREATE TEMPORARY TABLE(在事务中) - 使用
ENFORCE_GTID_CONSISTENCY=ON会拒绝上述操作
Q3: 一主多从和多主的适用场景?
答案:
- 一主多从:读多写少的场景,多个从库分担读压力。简单可靠。
- 双主(互为主从):写高可用场景,一个主写,另一个待切换。注意自增 ID 步长配置避免冲突。
- 多主多从(MGR):高可用 + 强一致场景,但复杂度高。