文章目录
- 半同步复制:数据安全和服务可用之间怎么权衡
- 一、三种复制方式对比
- 异步复制丢失数据的场景
- 二、半同步的原理
- 三、怎么配置
- 3.1 安装插件
- 3.2 开启
- 3.3 配置文件(永久)
- 四、关键参数
- 4.1 `rpl_semi_sync_master_timeout`
- 4.2 `rpl_semi_sync_master_wait_no_slave`
- 4.3 `rpl_semi_sync_master_wait_point`(MySQL 5.7+)
- AFTER_SYNC vs AFTER_COMMIT
- 五、监控
- 六、半同步的代价
- 6.1 增加主库写延迟
- 6.2 从库故障会影响主库
- 6.3 网络抖动敏感
- 七、8.0 的改进
- 7.1 参数重命名
- 7.2 性能改进
- 八、什么时候用
- 九、和 MGR 的关系
- 十、和 GTID 配合
- 十一、故障处理
- 11.1 半同步一直降级
- 11.2 主库写入变慢
- 十二、配置模板
- 小结
半同步复制:数据安全和服务可用之间怎么权衡
异步复制丢数据,全同步太慢。
半同步是折中方案——但有它自己的坑。
一、三种复制方式对比
| 方式 | 主库提交时 | 数据安全性 | 性能 |
|---|---|---|---|
| 异步(默认) | 写完本地 binlog 就返回 | 主库挂了丢数据 | 最好 |
| 半同步 | 等至少一个从库确认收到 | 好 | 中等 |
| 全同步 | 等所有从库都应用完 | 最好 | 最差 |
异步复制丢失数据的场景
1. 主库执行完事务,写 binlog 2. 主库返回"成功"给客户端 3. binlog 还没传给从库 4. 主库宕机 5. 切主 → 这个事务丢了已经返回成功了,数据却没了——这是最难受的情况。
二、半同步的原理
1. 主库执行事务,写 binlog 2. 主库把 binlog 发给从库 3. 从库收到并写入 relay log,给主库一个 ACK 4. 主库收到 ACK,才返回成功给客户端注意 ACK 的时机:从库收到 binlog 并落 relay log 就 ACK,
不需要应用完。
所以从库可能"收到了但还没执行"——
中间还有一小段风险窗口,但比异步小得多。
三、怎么配置
3.1 安装插件
主库:
INSTALL PLUGIN rpl_semi_sync_masterSONAME'semisync_master.so';从库:
INSTALL PLUGIN rpl_semi_sync_slaveSONAME'semisync_slave.so';8.0 的参数名改了(更中性):
INSTALL PLUGIN rpl_semi_sync_sourceSONAME'semisync_source.so';-- 主INSTALL PLUGIN rpl_semi_sync_replicaSONAME'semisync_replica.so';-- 从老的参数名在 8.0 还能用(会废弃警告)。
3.2 开启
-- 主库SETGLOBALrpl_semi_sync_master_enabled=ON;SETGLOBALrpl_semi_sync_master_timeout=1000;-- 毫秒-- 从库SETGLOBALrpl_semi_sync_slave_enabled=ON;从库要重启 IO 线程才生效:
STOP SLAVE IO_THREAD;STARTSLAVE IO_THREAD;3.3 配置文件(永久)
# 主库 rpl_semi_sync_master_enabled = ON rpl_semi_sync_master_timeout = 1000 rpl_semi_sync_master_wait_for_slave_count = 1 # 从库 rpl_semi_sync_slave_enabled = ON四、关键参数
4.1rpl_semi_sync_master_timeout
最重要的参数。
SETGLOBALrpl_semi_sync_master_timeout=1000;-- 1 秒意思是:等从库 ACK 最多等 1 秒,
超时了就降级为异步复制,正常返回给客户端。
这是"半"同步的关键——
它不会无限等待,保证可用性优先。
SHOWSTATUSLIKE'Rpl_semi_sync_master_status';-- ON = 半同步生效中-- OFF = 已降级为异步4.2rpl_semi_sync_master_wait_no_slave
SETGLOBALrpl_semi_sync_master_wait_no_slave=ON;-- 默认没有从库连接时怎么办:
| 值 | 行为 |
|---|---|
| ON(默认) | 没有从库时继续等待,直到超时降级 |
| OFF | 没有从库时立刻降级为异步 |
一般保持默认。
4.3rpl_semi_sync_master_wait_point(MySQL 5.7+)
控制 ACK 的时机:
SETGLOBALrpl_semi_sync_master_wait_point=AFTER_SYNC;-- 默认,推荐| 值 | 时机 | 特点 |
|---|---|---|
AFTER_SYNC | 写完 binlog 后、提交前等待 | 无损,推荐 |
AFTER_COMMIT | 提交后等待 | 有"幻读"问题 |
AFTER_SYNC vs AFTER_COMMIT
AFTER_COMMIT 的问题:
1. 主库提交事务(引擎层已提交) 2. 等待从库 ACK 3. 此时其他连接已经能看到这个事务的结果 4. 主库宕机,从库没收到 → 切主后数据没了 5. 但之前读到这个数据的应用以为成功了这叫"phantom read in failover"。
AFTER_SYNC 的做法:
1. 主库写 binlog 2. 等从库 ACK 3. 收到 ACK 后才提交到引擎层 4. 返回成功引擎层提交之前就保证了从库已收到,
切换后不会丢。
MySQL 5.7 起默认 AFTER_SYNC,这是无损半同步。
五、监控
SHOWSTATUSLIKE'Rpl_semi_sync%';主库关键指标:
| 指标 | 含义 |
|---|---|
Rpl_semi_sync_master_status | ON/OFF,是否降级 |
Rpl_semi_sync_master_clients | 有几个半同步从库 |
Rpl_semi_sync_master_tx_avg_wait_time | 平均等待 ACK 时间 |
Rpl_semi_sync_master_no_tx | 退化为异步的事务数← 重点 |
Rpl_semi_sync_master_yes_tx | 半同步成功的事务数 |
Rpl_semi_sync_master_timeouts | 超时次数 |
告警项:
Rpl_semi_sync_master_status = OFF→ 已降级Rpl_semi_sync_master_timeouts增长 → 网络或从库有问题
六、半同步的代价
6.1 增加主库写延迟
每个事务多一个网络往返。
| 网络 | 额外延迟 |
|---|---|
| 同机房 | 0.2~1 ms |
| 同城跨机房 | 1~3 ms |
| 跨城 | 10~30 ms |
同机房基本无感,跨城就有影响了。
6.2 从库故障会影响主库
从库挂了 → 收不到 ACK → 每个事务都要等 timeout(比如 1 秒)
→主库写性能暴跌。
这是半同步最大的风险点。
缓解:
- timeout 设小一点(比如 500ms~1s)
- 监控
Rpl_semi_sync_master_status,降级时立刻告警 - 至少配 2 个半同步从库
6.3 网络抖动敏感
网络一抖,主库写入就卡。
生产建议:主库和至少一个半同步从库放在同机房,
跨机房的从库用异步。
七、8.0 的改进
7.1 参数重命名
rpl_semi_sync_master_* → rpl_semi_sync_source_* rpl_semi_sync_slave_* → rpl_semi_sync_replica_* SHOW SLAVE STATUS → SHOW REPLICA STATUS老名字还能用(有 deprecation 警告)。
7.2 性能改进
8.0 对半同步的等待机制做了优化,
高并发下性能比 5.7 好不少。
5.7 有个著名的 bug:
半同步在高并发下因为互斥锁导致性能下降严重。8.0 修复了。
八、什么时候用
| 场景 | 建议 |
|---|---|
| 金融、支付、订单 | 用半同步 |
| 一般业务,能容忍秒级数据丢失 | 异步就够 |
| 跨地域容灾 | 同机房半同步 + 异地异步 |
| 从库只是做报表/备份 | 异步(别让它拖慢主库) |
典型架构:
主库(机房 A) ├─ 半同步 ─→ 从库 1(机房 A) ← 高可用切换目标 ├─ 异步 ─→ 从库 2(机房 B) ← 异地容灾 └─ 异步 ─→ 从库 3(机房 B) ← 报表查询只让高可用切换的目标用半同步,别的所有从库都用异步。
九、和 MGR 的关系
MySQL Group Replication(MGR)内部用的就是类半同步机制,
但它是一套完整的组复制方案:
| 维度 | 半同步复制 | MGR |
|---|---|---|
| 故障切换 | 要外部工具(MHA/Orchestrator) | 内置自动切换 |
| 一致性协议 | 简单的 ACK | Paxos 变体 |
| 写冲突检测 | 无 | 有 |
| 多主 | 不支持 | 支持(但少用) |
| 部署复杂度 | 低 | 中 |
如果只需要"主库写不丢",半同步够用且简单。
如果要自动故障切换,考虑 MGR。
十、和 GTID 配合
gtid_mode = ON enforce_gtid_consistency = ON有了 GTID,故障切换时能准确判断哪个从库的数据最新。
SELECT@@gtid_executed;-- 对比各实例的 GTID 集合强烈建议开启 GTID,它让切换变得可靠得多。
十一、故障处理
11.1 半同步一直降级
SHOWSTATUSLIKE'Rpl_semi_sync_master_status';-- OFF排查:
-- 1. 有几个从库连着SHOWSTATUSLIKE'Rpl_semi_sync_master_clients';-- 2. 从库的 IO 线程状态SHOWSLAVESTATUS\G-- Slave_IO_Running: Yes?-- 3. 从库有没有开半同步SELECT@@rpl_semi_sync_slave_enabled;-- 4. 网络延迟ping/mtr 检查11.2 主库写入变慢
SHOWSTATUSLIKE'Rpl_semi_sync_master_tx_avg_wait_time';-- 这个值很大说明从库响应慢可能原因:
- 从库磁盘 IO 打满(写 relay log 慢)
- 网络延迟大
- 从库负载高
应急:先降级为异步恢复主库性能。
SETGLOBALrpl_semi_sync_master_enabled=OFF;十二、配置模板
# ===== 主库 ===== [mysqld] server_id = 1 log_bin = mysql-bin gtid_mode = ON enforce_gtid_consistency = ON plugin_load = "rpl_semi_sync_master=semisync_master.so" rpl_semi_sync_master_enabled = ON rpl_semi_sync_master_timeout = 1000 rpl_semi_sync_master_wait_point = AFTER_SYNC rpl_semi_sync_master_wait_for_slave_count = 1 # ===== 从库(半同步的那一个)===== [mysqld] server_id = 2 relay_log = relay-bin read_only = ON plugin_load = "rpl_semi_sync_slave=semisync_slave.so" rpl_semi_sync_slave_enabled = ON小结
| 维度 | 说明 |
|---|---|
| 解决什么 | 异步复制主库宕机丢数据 |
| ACK 时机 | 从库收到 binlog 并落 relay log |
| 关键参数 | rpl_semi_sync_master_timeout |
| 等待点 | 用 AFTER_SYNC(无损),不用 AFTER_COMMIT |
| 代价 | 每个事务多一个网络往返 |
| 最大风险 | 从库故障时主库写入卡顿 |
| 监控重点 | Rpl_semi_sync_master_status、_timeouts |
三条实用建议:
- 只对高可用切换的目标从库开半同步,其他从库用异步
- 半同步从库和主库放同机房,避免跨机房抖动影响主库写入
wait_point用 AFTER_SYNC(MySQL 5.7+ 默认就是这个),
别手动改成 AFTER_COMMIT
什么时候不用:
- 数据丢了能重放(比如可以从消息队列重建)
- 从库经常故障/网络不稳
- 对写入延迟极度敏感且数据能容忍丢失
半同步是个"用一点性能换数据安全"的取舍,
在支付、订单这类场景很值得;
在日志、埋点这类场景没必要。