Skip to content
Go back

MySQL半同步复制——主库宕机后数据不丢失的保障

MySQL 半同步复制:主库宕机不丢已 ACK 的数据

一句话结论(30s)

半同步复制的本质是在主库返回客户端成功之前,至少一个从库已把 binlog 写入 relay log 并 ACK,因为异步复制在主库”返回成功”到”发送从库”之间存在丢数据的窗口。关键设计是 after_sync 模式:引擎层提交也等从库 ACK 后才进行,修复了 after_commit 下”已提交未同步”被读到的问题。权衡在于:用单从库 ACK 的网络延迟换取已 ACK 事务零丢失,超时后降级异步防止网络抖动阻塞写入。

核心原理(2min)

异步复制里主库写 binlog → 返回成功 → 再异步发给从库,若在主库返回成功与发送之间宕机,客户端认为已提交但数据永远丢失。半同步复制 after_sync(MySQL 5.7+ 默认)让主库写 binlog 后先发给从库、等至少一个从库 ACK”已写入 relay log”、再提交引擎层事务并返回成功,因此主库宕机时那个从库拥有最后事务、切过去不丢已 ACK 数据。after_commit 的旧问题是主库先提交再等 ACK,其他事务可能读到”已提交但未同步”的数据。若超过 rpl_semi_sync_master_timeout(默认 10s)没收到 ACK,主库降级为异步复制直接提交返回,Rpl_semi_sync_master_status 显示 OFF 表示已降级。

底层深入(5-10min)

异步复制的问题

异步复制:
  主库写 binlog → 返回客户端成功 → 异步发送给从库
  
  ↑ 如果主库在"返回客户端成功"和"发送给从库"之间宕机
  → 客户端自认为事务已提交,但从库永远收不到
  → 故障切换到从库后数据丢失

💭 想一想:异步复制”丢”的是哪一段?——是主库”返回成功”到”把 binlog 发出去”之间的那一小段。客户端已经收到成功、以为写进去了,可这事务还没到从库;主库一宕,这段就永久消失。半同步要堵的就是这个”已对客户端承诺、却还没送达从库”的窗口。

半同步复制:至少一个从库 ACK

半同步复制 (after_sync):
  主库写 binlog → 发送给从库 
  → 等待至少一个从库确认 (ACK: "我已写入 relay log")
  → 主库返回客户端成功

在主库返回客户端成功之前,至少一个从库已经收到了完整的 binlog 事件。 主库宕机 → 这个从库拥有最后的事务 → 切换到它做主库 → 客户端认为已提交的事务不丢失。

after_sync vs after_commit

after_sync(MySQL 5.7+ 默认):从库确认写入 relay log 后主库才提交引擎层事务。

after_commit(旧版):主库先提交引擎事务再等从库 ACK。问题是从库确认前主库已提交——其他事务可能读到了”已提交但尚未同步”的数据——如果此时主库宕机切换,其他客户端读到的不一致数据。

after_sync 修复了这个窗口:引擎层提交也等从库 ACK 后才进行。

💭 想一想:after_commit 的问题到底出在哪?——出在”提交”和”同步”的顺序反了:它先让引擎层提交、再等 ACK,于是从库确认之前,主库已有”已提交但未同步”的数据,其他事务能读到这份”主从还不一致”的状态;一旦此刻切主,别人读到的就成了幻象。after_sync 把等 ACK 挪到引擎提交之前,杜绝了这个读得到但同步不了的时间窗。

超时降级

SET GLOBAL rpl_semi_sync_master_timeout = 10000;  -- 10s 超时

如果发送给从库后超过 timeout 没有收到 ACK → 主库降级为异步复制——不等从库 ACK 直接提交并返回。防止半同步在网络抖动时阻塞所有写入。

💭 想一想:为什么必须允许”降级”,降级又牺牲了什么?——因为半同步是拿延迟换可靠性,若从库一直不回 ACK,主库就会一直卡着等,等于把可用性交给网络;降级是”兜底放行”、优先保写入可用。代价是降级窗口内又回到了异步语义,这段数据可能丢——所以”半同步不丢”严格说是”不丢已 ACK 的数据”,而非”任何时刻都不丢”。

SHOW STATUS LIKE 'Rpl_semi_sync_master_status' = ON 表示当前半同步工作中,OFF 表示已降级为异步。

总结

异步半同步 (after_sync)全同步
数据丢失可能0(已 ACK 事务)0
性能影响单从库 ACK 延迟所有从库 ACK 延迟
主库宕机丢失窗口期数据不丢已 ACK 数据不丢任何数据

章末提问

追问 1:半同步复制保证的”不丢”具体指什么?什么情况下还是会丢?

回答思路:结论先行——保证的是”已 ACK 的事务不丢”,但降级窗口内的事务仍可能丢。因为主库写 binlog 后等至少一个从库 ACK”已写入 relay log”才返回成功,主库宕机时那个从库已有最后事务、切过去不丢;但若超过 rpl_semi_sync_master_timeout 降级为异步,降级期间写入又回到”未同步就返回”的状态,这段数据依然可能丢。

追问 2:after_sync 相比 after_commit 修复了什么?

回答思路:结论先行——修复了”引擎已提交、但从库还没收到”时其他事务读到不一致数据的问题。因为 after_commit 先提交引擎事务再等 ACK,从库确认前主库已有”已提交未同步”的数据可被读到,此时切主会导致别人读到幻象;after_sync 把等 ACK 挪到引擎提交之前,只有从库确认后才提交并返回,堵住了这个窗口。

追问 3:为什么要有超时降级?降级会带来什么风险、如何缓解?

回答思路:结论先行——降级是为了在网络抖动时不阻塞写入、保住可用性;风险是降级期间退回异步语义、可能丢数据。因为若从库一直不回 ACK 主库就无限等,写入全卡死;降级是优先保可用。缓解手段:把 rpl_semi_sync_master_timeout 调大减小降级窗口、配合 rpl_semi_sync_master_wait_for_slave_count=2 等 2 个从库 ACK 进一步降低丢失风险,代价是写入延迟增加。


Share this post on:

Previous Post
MySQL 存储引擎架构五层模型:表空间→段→区→页→行
Next Post
MySQL两阶段提交与崩溃恢复