主从复制
→ 返回 数据库基础
主从复制(Replication)是 MySQL 高可用与读写分离的基础:主库(Master)处理写入,变更同步到一个或多个从库(Replica),从库提供读服务。
复制原理
主库(Master) 从库(Replica)
┌─────────────────────┐ ┌─────────────────────────────┐
│ Client 写入 │ │ IO Thread │
│ ↓ │ │ 连接主库,拉取 binlog │
│ 执行 SQL → 提交事务 │ │ 写入本地 Relay Log │
│ ↓ │ binlog 流 │ ↓ │
│ 写入 binlog ──────►│──────────────►│ SQL Thread │
└─────────────────────┘ │ 重放 Relay Log 中的 SQL │
│ 更新从库数据 │
└─────────────────────────────┘
三个线程:
| 线程 | 所在节点 | 职责 |
|---|---|---|
| Dump Thread | 主库 | 监听从库连接,推送 binlog 事件 |
| IO Thread | 从库 | 连接主库,接收 binlog → 写 Relay Log |
| SQL Thread | 从库 | 读取 Relay Log,重放 SQL 更新本地数据 |
binlog 三种格式
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
STATEMENT | 原始 SQL 语句 | 日志体积小 | NOW()、UUID() 等非确定性函数在从库执行结果不同,数据不一致 |
ROW | 每行数据的前后值 | 数据精确,CDC 工具必须用此模式 | 大事务时日志体积极大(如 DELETE 100万行) |
MIXED | 默认 STATEMENT,不安全场景自动切 ROW | 兼顾体积与安全 | 行为复杂,排查问题困难 |
生产推荐 ROW 模式,配合 binlog_row_image=FULL,并压缩 binlog。
# my.cnf
binlog_format = ROW
binlog_row_image = FULL
sync_binlog = 1 # 每次事务提交都刷盘,防止主库宕机丢失 binlog
innodb_flush_log_at_trx_commit = 1 # redo log 同步刷盘,双一配置三种同步模式
| 模式 | 流程 | 数据安全 | 性能 |
|---|---|---|---|
| 异步复制(默认) | 主库提交后立即返回,不等从库确认 | 主库宕机可能丢失已提交事务 | 高 |
| 半同步复制 | 主库等待至少一个从库写入 Relay Log 后才返回 | 至少一份副本,安全性高 | 中(主库有等待延迟) |
| 增强半同步(After Sync) | 主库 binlog 刷盘 → 从库 ACK → 主库提交 | 最安全,从库先确认 | 较低 |
-- 安装半同步插件(主库)
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 1000; -- ACK 超时 1s,退化为异步
-- 从库
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;GTID 复制(推荐)
GTID(Global Transaction ID):每个事务在全局唯一标识,格式 server_uuid:transaction_id。
优点:
- 从库重新指向新主库时,无需手动指定 binlog 文件名和位点(
CHANGE MASTER TO自动处理) - 轻松实现主从切换和故障恢复
# my.cnf(主从都要开)
gtid_mode = ON
enforce_gtid_consistency = ON-- 基于 GTID 的从库配置
CHANGE MASTER TO
MASTER_HOST = '192.168.1.10',
MASTER_USER = 'replica',
MASTER_PASSWORD = 'password',
MASTER_AUTO_POSITION = 1; -- 自动基于 GTID,无需指定 binlog 文件和位点
START SLAVE;
SHOW SLAVE STATUS\G主从延迟
产生原因
主库:多线程并发写入,binlog 产生快
从库:SQL Thread 默认单线程重放,无法并发
主库写入量 > 从库重放速度 → 延迟累积
常见触发场景:
- 主库大事务(批量 UPDATE/DELETE 百万行)
- 主库 DDL(ALTER TABLE 长时间锁表后的大量积压)
- 从库服务器性能低于主库
- 网络抖动导致 IO Thread 重连
查看延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 30 ← 延迟 30s
-- Relay_Master_Log_File / Exec_Master_Log_Pos ← 对比主库位点解决方案
| 方案 | 说明 |
|---|---|
| 并行复制(MTS) | slave_parallel_workers = 8(MySQL 5.7+ 支持按事务/按 WRITESET 并行重放) |
| WRITESET 并行 | slave_parallel_type = WRITESET(MySQL 8.0+,最优,无依赖关系的事务并行执行) |
| 避免大事务 | 批量操作分批处理(每批 1000 行),减少单事务 binlog 体积 |
| 从库升配 | 提升从库 CPU/磁盘 IO 性能,匹配主库写入速度 |
| 半同步超时降级 | 半同步场景监控 rpl_semi_sync_master_no_tx,延迟高时及时告警 |
# my.cnf(从库)
slave_parallel_type = WRITESET
slave_parallel_workers = 8
slave_preserve_commit_order = 1 # 保证事务提交顺序一致读写分离
主库写,从库读,分散压力:
应用
├── 写操作(INSERT/UPDATE/DELETE)→ 主库
└── 读操作(SELECT) → 从库1 / 从库2 / 从库3(负载均衡)
实现方案
| 方案 | 说明 |
|---|---|
| 应用层路由 | 代码中配置主从数据源,手动指定;灵活但侵入业务代码 |
| 中间件代理 | MyCat / ShardingSphere-Proxy / ProxySQL,应用无感知 |
| Spring AbstractRoutingDataSource | 动态数据源,结合 @ReadOnly 注解切换 |
读写分离一致性问题
主从延迟期间,从库读到旧数据:
| 场景 | 处理策略 |
|---|---|
| 写后立即读(如下单后查订单) | 强制路由到主库(@Primary / 事务内读) |
| 普通查询 | 允许轻微延迟,走从库 |
| 关键一致性读 | 读主库或等待 WAIT_FOR_EXECUTED_GTID_SET |
// Spring 读写分离示例(AbstractRoutingDataSource)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}
@Aspect
public class DataSourceAspect {
@Before("@annotation(ReadOnly)")
public void setReadDataSource() {
DataSourceContextHolder.set("replica");
}
@After("@annotation(ReadOnly)")
public void clearDataSource() {
DataSourceContextHolder.clear();
}
}高可用架构
一主一从(最简单)
Client → Master(读写)
└── Replica(读 + 备份)
一主多从
Client → Master(写)
├── Replica-1(读)
├── Replica-2(读)
└── Replica-3(备份/异地容灾)
MHA(Master High Availability)
主库宕机时,MHA 自动选举延迟最小的从库提升为主库,并修复其他从库的复制链路:
MHA Manager(监控主库心跳)
│ 主库宕机
▼
选举延迟最小的 Replica → 提升为新 Master
更新其他 Replica 的 CHANGE MASTER TO → 指向新 Master
MySQL InnoDB Cluster(官方方案)
基于 Group Replication(组复制),自动选主、自动故障恢复,支持多主模式。
常用命令
-- 主库:查看 binlog 状态
SHOW MASTER STATUS;
SHOW BINARY LOGS;
-- 从库:查看复制状态(重点关注 Seconds_Behind_Master)
SHOW SLAVE STATUS\G
-- 从库:暂停/启动复制
STOP SLAVE;
START SLAVE;
-- 跳过一个错误事件(临时处理复制错误,慎用)
STOP SLAVE;
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
START SLAVE;
-- 基于 GTID 跳过指定事务(更精确)
STOP SLAVE;
SET GTID_NEXT = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:N';
BEGIN; COMMIT;
SET GTID_NEXT = 'AUTOMATIC';
START SLAVE;