MySQL原生半同步不等于零丢失,因其仅保证从库接收并刷盘relay log,未校验事务是否执行;超时退化异步且无告警;增强半同步需严格配置才有效;MGR虽全同步但限制多;生产应组合配置+人工校验位点。

MySQL 主从切换时无法靠单点配置实现“零丢失”,全同步复制(Group Replication)和增强半同步(lossless semi-sync)是两种不同层级的保障手段,适用场景和约束条件差异极大。盲目启用反而可能引入不可控延迟或脑裂风险。
为什么 MySQL 原生半同步 ≠ 零丢失
半同步复制只保证至少一个从库 Received 并 flushed to relay log,不校验 Exec_Master_Log_Pos 是否已执行。这意味着:
- 主库 crash 后若选中一个
Read_Master_Log_Pos == Exec_Master_Log_Pos不成立的从库切主,未执行事务直接丢失 -
rpl_semi_sync_master_timeout默认 10 秒,超时即退化为异步,且无告警机制 - 即使
Rpl_semi_sync_master_status显示ON,Rpl_semi_sync_master_no_tx非零就说明已有事务降级提交 - IO 线程写入 relay log ≠ SQL 线程已 apply,而切换决策常只看
Seconds_Behind_Master,它在 SQL 线程卡住时会误报为 0
增强半同步(lossless semi-sync)的关键配置与陷阱
MySQL 5.7+ 的增强半同步将事务提交时机从 after_commit 改为 after_sync:主库先写 binlog、等 ACK、再 commit。这避免了主库 crash 后从库少一个事务的问题,但前提是:
- 必须同时启用
rpl_semi_sync_master_enabled = ON和rpl_semi_sync_slave_enabled = ON,缺一不可 - 从库需运行
START SLAVE IO_THREAD(不是START SLAVE),否则无法上报 ACK -
sync_binlog = 1和innodb_flush_log_at_trx_commit = 1必须开启,否则主库崩溃仍可能丢 binlog 或 redo - 不要依赖
SHOW VARIABLES LIKE 'rpl_semi_sync%'判断是否生效,要查SHOW STATUS LIKE 'Rpl_semi_sync%'实时状态
Group Replication(MGR)才是真·全同步,但代价明确
MGR 是基于 Paxos 的多节点共识协议,要求多数派确认后才返回客户端。它能真正防止切换丢数据,但有硬性前提:
- 必须使用
binlog_format = ROW,SBR 不被允许 - 所有节点需开启
gtid_mode = ON、enforce_gtid_consistency = ON - 事务必须满足“无外部依赖”:不能含
SELECT ... FOR UPDATE、CREATE TEMPORARY TABLE、用户定义函数等 - 网络分区时,少数派节点自动
read_only = ON,多数派继续服务——但若多数派崩溃,整个集群不可写 - 写性能下降明显,尤其跨机房部署时 RTT 直接影响吞吐,
group_replication_consistency = BEFORE_ON_PRIMARY_FAILOVER才能保切主不丢
生产环境最务实的数据不丢策略
别指望靠一个开关解决所有问题。真实可用的防线是组合动作:
- 主库强制
sync_binlog = 1+innodb_flush_log_at_trx_commit = 1,确保本地不丢 - 部署 ≥3 个从库,其中 1 个设为
super_read_only = ON+skip_slave_start = 1,只收日志不执行,专用于切主前快速校验位点 - 切换前用脚本检查:
SELECT @@global.read_only, Seconds_Behind_Master, Slave_SQL_Running_State FROM information_schema.slave_status,必须满足Seconds_Behind_Master = 0且Slave_SQL_Running_State = 'Slave has read all relay log' - 切主后立刻执行
STOP SLAVE; RESET SLAVE ALL;,避免旧复制关系残留引发环路
最容易被忽略的一点:所有自动化切换工具(MHA、Orchestrator、ProxySQL)默认都不校验 Exec_Master_Log_Pos 与 Read_Master_Log_Pos 是否相等。这个差值就是你切换时实际丢失的数据量,必须人工介入或定制脚本兜底。


















