资讯动态

MySQL集群一致性:主从延迟、半同步复制与故障切换实战排查

发布时间:2026/10/21 8:13:48 来源:尧图企业网站定制
只要你的 MySQL 集群上过生产大概率都撞到过集群一致性问题这个词。它通常不会以一条报错的形式出现而是藏在业务反馈里读写分离刚改完数据下一页又读到旧的主备切换之后订单状态回退了两边导出来的报表数字对不上。我在排查群里见过不少朋友一听到一致性第一反应就是换方案、加中间件结果问题反而更多。这篇内容不教你怎么一步到位上强一致方案而是把 MySQL 集群一致性问题的成因、常见方案的取舍、能落地验证的手段和日常运维的坑一次讲清楚。适合正在维护 MySQL 主从或集群结构的 DBA、后端开发也适合被延迟和数据对不上问题折腾过的朋友。1. 项目概述要的到底是一致还是可用1.1 集群不一致的四个典型现象先说最直观的四个现象很多团队都是在这些场景里第一次意识到一致性问题。第一个是读写分离读到旧数据。应用写完后立刻去查请求被路由到从库返回的还是旧值。用户刷新一下又好了但是对账或者客服侧就会看到数据反复横跳。这种问题最容易在刚上线读写分离时爆发。第二个是主备切换丢事务。主库磁盘故障或者进程挂掉DBA 把从库提升上来结果发现最近几秒提交的订单记录没有了。故障演练时一对比 binlog 和业务日志丢了事务业务方立刻炸毛。这类问题在高可用脚本不完善时非常常见。第三个是双主互相覆盖。两个节点同时接受写请求分别更新同一行数据没有冲突检测机制的话后写入的会静默覆盖前者旧版本的变更。自增主键还可能在两边生成相同的值导致同步直接报 duplicate key 中断。第四个是数据校验不一致。拿校验工具全量比对主从发现几百行甚至几千行差异但打开延迟监控看 Seconds_Behind_Master 一直是 0。这就说明问题不是简单延迟而是复制链路里有事务丢失、被跳过或者双写冲突长期积累。这四个现象看着完全不一样但根子上都指向同一个事实数据从主节点传播到从节点不是瞬间完成的这条传播链路上任何一个环节出问题都会放大不一致。1.2 不一致问题从哪来CAP 是底色要理解 MySQL 主从集群的一致性得先接受一个前提MySQL 原生的主从复制模型本身就不是分布式强一致方案。它默认走的是异步复制主库提交事务成功后立刻返回客户端成功然后后台再把 binlog 推给从库。这个设计换来的是高性能和高可用代价就是数据一致性窗口。你可以想象成两套账本。主库的记账员已经给客户签字确认这笔钱到账了分店的记账员还在路上账本上没有这笔记录。客户去分店查余额自然查不到。对一个记账系统来说这个窗口可能只有几毫秒对大多数业务没问题但一旦延迟堆到秒级甚至分钟级问题就显现了。CAP 理论在这里就是底色。在分区发生时一个分布式系统必须在一致性和可用性之间做选择。MySQL 集群默认用了大部分互联网业务最熟悉的组合高可用 最终一致。同步复制、强一致方案都存在但都要付出更高成本。所以第一步不是问怎么做到绝对一致而是问业务能容忍多大的数据窗口。这个问题想清楚下面的方案选择才有方向。2. 机制拆解不一致是在哪一环节被放大的2.1 主从复制链路的三个关键窗口主从复制的完整链路可以分成四段应用写入主库主库把事务写进 binlog 并提交存储引擎主库的 dump 线程把 binlog 传给从库从库的 IO 线程接收并写入 relay log从库的 SQL 线程异步回放 relay log把数据落到从库引擎里。链路里有三个窗口最值得盯。第一个窗口是主库 binlog 的落盘窗口。MySQL 通过 sync_binlog 参数控制 binlog 刷盘策略。如果设为 0MySQL 依赖操作系统刷盘主库进程崩溃或整机宕机时最后一批 binlog 可能没真正落到磁盘。机器重启后这些事务就不见了从库自然也没收到。对持久性要求高的场景sync_binlog 必须设为 1。第二个窗口是从库收到 relay log 但还没落盘。从库 IO 线程把日志写进 relay log 后同样存在操作系统缓存问题。如果从库崩溃relay log 可能丢失一部分主库会重新推送一般影响不大但要留意磁盘写入能力不足导致的 IO 线程堆积。第三个窗口是 SQL 线程回放这是主从延迟的主要来源。SQL 线程把 relay log 里的事务一条条执行到从库引擎它是一个独立的异步过程。从库可以开并行复制但大事务、DDL、锁等待依然会造成回放阻塞延迟就从这里长出来。很多人以为主从延迟是网络慢其实大部分时间都耗在回放上。主库执行一个 UPDATE 可能用了 100 毫秒从库回放同一个大事务可能要几十秒。这期间主库还在继续提交新事务relay log 越积越多延迟就滚雪球一样变大。2.2 半同步复制的原理与伪一致陷阱异步复制的问题在于主库说自己成功但日志还没到从库。半同步复制就是为了缩小这个窗口。半同步的核心逻辑是主库提交事务时必须等待至少一个从库确认收到了 binlog才能给客户端返回成功。注意关键词是收到 binlog不是回放完成。也就是说从库的 relay log 里有了这个事务半同步就认为 OK 了哪怕这个事务还没执行到从库的数据文件里。以常见的 AFTER_SYNC 模式为例主库先把事务写进 binlog然后等待从库 ACK收到确认后再提交存储引擎。这种模式的好处是即使主库在事务引擎提交前宕机从库里也已经有了这条日志提升从库后事务不会丢。这是 MySQL 5.7 之后的主推配置。但半同步有一个知名陷阱超时降级。rpl_semi_sync_master_timeout 默认是 10 秒如果 10 秒内没有从库返回 ACK主库会自动把复制模式降级为异步。降级之后主库不再等待任何人直接提交返回。这个降级动作本身是为了保证可用性但它把一致性窗口瞬间又放大了。更头疼的是如果不监控状态变量整个团队根本不知道半同步已经静默失效。所以对半同步的理解要到位它防的是提交后 binlog 一点没复制就丢日志但不防从库回放太慢导致读到旧数据也不防超过 timeout 后的降级窗口。依赖半同步之前先确认监控有没有覆盖降级事件。2.3 组复制与分布式事务的适用边界如果业务确实需要比半同步更强的一致性保证很多团队的下一站是 MySQL Group Replication也就是 MGR。MGR 的底层是一套类 Paxos 的组共识协议事务需要经过组内多数派节点确认才算真正提交。它的价值在于每个节点对事务的顺序达成一致任何一个节点上发生的写入要么被组接受要么被拒绝配合自动选主机制高可用切换时更不容易丢数据。但 MGR 不是银弹。它要求网络稳定节点间往返时延不能太高跨机房部署时这个问题尤其明显。性能上它需要每个事务都做一次组内协商吞吐上限受最慢节点影响不像异步复制那样主库写完就完事。多主模式可以接受多个节点写入但冲突检测机制是发现冲突直接回滚应用层如果没有处理线上就会冒出大量报错。分布式事务 XA 是另一种思路通过两阶段提交让多个 MySQL 实例的写入保持原子性。这里要给你一个非常直接的建议能不用就别用除非业务必须在一个事务里跨库更新。XA 的性能开销、协调者单点、故障恢复复杂度都比普通事务高一个量级很多团队上线后都要为此专门开发补偿机制。所以你会发现大部分高并发业务最终选的不是绝对强一致而是异步复制 半同步缩小窗口 快速切换 定期校验修复。这个组合在一致性和成本之间找到了一个相对务实的平衡点。3. 实操实录用最小集群验证一致性窗口说了这么多原理不实操验证一遍很难建立体感。下面用一主一从的最小集群带你走一遍搭建半同步环境、制造延迟、模拟故障切换亲眼看看不一致是哪里来的。3.1 搭建一套带 GTID 的半同步集群先准备两台实例主库和从库。我建议从一开始就打开 GTID后面做切换和对比会非常方便。主库的 my.cnf 里至少要有这些参数server-id 1 log_bin /data/mysql/binlog binlog_format ROW gtid_mode ON enforce_gtid_consistency ON sync_binlog 1 innodb_flush_log_at_trx_commit 1从库除了 server-id 换成 2还需要加上server-id 2 relay_log /data/mysql/relaylog read_only ON gtid_mode ON enforce_gtid_consistency ON从库配置 read_only 是必须的防止误写入导致数据分叉。然后做主从关系建立8.0 后 CHANGE MASTER TO 和 CHANGE REPLICATION SOURCE TO 都可以用新环境建议用后者CHANGE REPLICATION SOURCE TO SOURCE_HOST主库IP, SOURCE_PORT3306, SOURCE_USERrepl, SOURCE_PASSWORD密码, SOURCE_AUTO_POSITION1; START REPLICA;基础复制跑通后加载半同步插件主库和从库都要执行INSTALL PLUGIN rpl_semi_sync_master SONAME semisync_master.so; INSTALL PLUGIN rpl_semi_sync_slave SONAME semisync_slave.so;主库设置SET GLOBAL rpl_semi_sync_master_enabled 1; SET GLOBAL rpl_semi_sync_master_timeout 3000;从库设置SET GLOBAL rpl_semi_sync_slave_enabled 1; STOP REPLICA; START REPLICA;然后可以通过状态变量确认半同步是否生效SHOW GLOBAL STATUS LIKE Rpl_semi_sync_master_status; SHOW GLOBAL STATUS LIKE Rpl_semi_sync_master_clients;如果 Rpl_semi_sync_master_status 是 ON说明主库正处半同步模式。整个过程不复杂但要注意插件加载顺序和从库重启复制线程很多人只改参数忘记重启半同步一直没有真正激活。3.2 制造主从延迟实测读到的数据有多旧集群稳定后我们来人为制造一个延迟场景。延迟最容易被大事务放大所以在主库执行一个大范围更新UPDATE 大表 SET remark batch_update WHERE id IN (SELECT id FROM 大表 LIMIT 2000000);这个大事务在从库回放期间主库继续正常接收业务请求。此时我开一个循环脚本往主库插入带序号的数据同时不停在从库查询最大 IDfor i in $(seq 1 10000); do mysql -h主库 -e INSERT INTO orders(user_id, amount) VALUES ($i, 100); max_id$(mysql -h从库 -N -e SELECT MAX(id) FROM orders;) echo inserted_on_master$i, max_on_slave$max_id sleep 0.1 done结果会非常直观主库已经插入到 5000从库最大 ID 还停在 1200。这个差值就是业务读取到的数据延迟窗口。配合下面的状态查看一眼就能看到复制已经在排队SHOW REPLICA STATUS\G -- 重点看 Seconds_Behind_Master、Retrieved_Gtid_Set、Executed_Gtid_SetRetrieved_Gtid_Set 是从库已经拉取到 relay log 的 GTID 集合Executed_Gtid_Set 是已经回放完成的 GTID 集合。两个集合的差就是真正造成数据滞后的部分。这里要专门提醒一句Seconds_Behind_Master 不是实时靠时钟算出来的准确值它依赖主从日志时间戳遇到长时间未提交事务或者从库 SQL 线程处于半空闲时它可能不准甚至为 NULL。所以排查时永远要结合 GTID 差值一起看。这个实验做完你就能准确告诉业务方当前从库最多落后主库 N 个事务读写分离场景下用户最多会读到 N 个事务之前的数据。有了这个可量化的窗口评估一致性方案的收益就有依据了。3.3 模拟故障切换验证会不会丢数据延迟实验做了再做一次更刺激的主库直接宕机看切换后丢不丢数据。先确保应用已经暂停写入。然后用 kill -9 直接杀主库进程模拟非优雅宕机。这种模拟下主库来不及刷盘、来不及通知从库最接近事故现场。此刻观察从库的状态Relay 日志回放会慢慢追到 Relay_Master_Log_File 的末尾但主库已经断开不可能再推送新日志。手动提升从库前先确认两个信息SHOW REPLICA STATUS\G -- 确认 Replica_IO_Running: NoReplica_SQL_Running: Yes且 Executed_Gtid_Set 不再变化然后让从库脱离复制关系并开放可写STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only OFF;提升之后回到旧主库的 binlog 里找到宕机前最后一个提交事务的 GTID再对比新主库实际的 gtid_executed缺失的那部分事务就是这次故障切换丢失的数据量。你可以在旧主库 binlog 上执行SHOW BINLOG EVENTS IN 旧主库最后的binlog文件 FROM 最后位置;这样就能精确数出哪些事务没有复制过去。半同步模式下大部分刚提交的事务已经在从库 relay log 里但因为 timeout 窗口的存在最后一个或几个事务仍然可能丢失。这个实验在绝大多数环境里都能测出非预期的数据缺口所以生产环境的切换脚本绝不能只看进程起来了就算成功必须做数据校验。4. 常见问题排查与运维避坑4.1 高频问题与排查动作速查表下面这张表我整理自平时遇到的比较典型的线上问题可以收藏起来当排查手册用。现象可能原因优先排查动作Seconds_Behind_Master 长期为 NULL复制线程中断或状态异常看 Replica_IO_Running / Replica_SQL_Running再查看错误日志延迟持续增长不收敛大事务回放慢、从库负载高、并行复制线程不足SHOW PROCESSLIST 看回放线程当前执行语句结合慢日志定位半同步状态从 ON 变 OFF超过 rpl_semi_sync_master_timeout 自动降级查 Rpl_semi_sync_master_no_sync_count确认从库 IO 线程是否断开复制中断报错 duplicate key切换后 GTID 冲突或双主写入冲突对比主从 gtid_executed找重复事务来源必要时重建复制relay log 损坏磁盘异常或从库崩溃未恢复从失败位置重置中继日志或重新 CHANGE REPLICATION SOURCE TO数据校验不一致但延迟为 0binlog_format 不是 ROW、使用非确定性函数、有人手工改从库校验 GTID 集合检查 binlog_format 参数核对白名单过滤规则切换后丢事务没开半同步、sync_binlog0、从库没追平就开放写切换前检查 Executed_Gtid_Set切换后立即做行数校验这里的核心思想只有一个看到表面现象后不要急着解表先用 GTID 集合把主库有而从库没有的事务找出来再顺着这个集合去定位环节。4.2 日常巡检的三个核心指标日常运维中我建议团队只盯三个核心指标抓多了反而会分散注意力。第一个是半同步状态。主库上持续观察SHOW GLOBAL STATUS LIKE Rpl_semi_sync_master_status;只要这个值为 OFF就意味着集群已经退化到异步复制必须立刻告警。同时关注计数器SHOW GLOBAL STATUS LIKE Rpl_semi_sync_master_no_sync_count;这个数字代表有多少事务是在降级状态下提交的它越大数据丢失风险越高。第二个是延迟趋势。不要只看瞬时 Seconds_Behind_Master 是否为 0要看延迟量的曲线走向。一次大促或者一轮批量任务延迟可能从 0 冲到几十秒只要它能逐步下降恢复收敛就没有大问题如果长时间横盘不降甚至缓慢爬升那多半有回放卡点。第三个是 GTID 差值。在主从两边分别查 gtid_executed用 GTID_SUBTRACT 计算差集SELECT GTID_SUBTRACT( (SELECT GLOBAL.gtid_executed ON 主库), (SELECT GLOBAL.gtid_executed ON 从库) );差集是否存在比差集大小更关键。只要有差集就说明还有事务没到从库延迟为 0 也不代表完全对齐。这比盯着一个秒数靠谱得多。4.3 操作禁忌与经验提醒再分享几条我个人踩过的坑希望大家不要再踩。第一条不要把 binlog_format 改成 STATEMENT 来省空间。ROW 格式虽然日志量大但回放结果确定。STATEMENT 格式下存储过程、随机函数、带有不确定条件的事务在主从回放时可能产生完全不同的结果校验工具跑出来的差异会让人怀疑人生。为了省点磁盘空间引出一堆一致性事故非常不划算。第二条不要在主库执行超大事务的同时做数据校验或者备份。大事务本身会拉高延迟校验工具的读取和比对会额外消耗从库资源两个动作叠加延迟可能分分钟爬升到分钟级。我一般建议大事务安排在业务低峰执行校验任务和 DDL 错开且单独观察延迟走势。第三条不要在没有追平复制的情况下对从库开放可写。故障切换时如果应用接入了新的主库而这个新主库的 relay log 还没完全回放那任何写入都会混进旧事务 新事务的序列里极难清理。所以提升从库前必须确认 Executed_Gtid_Set 已经追平至不能变化最好用脚本固定校验而不是肉眼判断。第四条半同步不是万能的防丢日志但不防丢提交。超时降级、从库回放落后、多节点网络分区这些场景下半同步都不保证任何事。业务侧需要配套限流、降级和重试机制而不是把宝全押在数据库上。5. 一点个人体会与继续深挖的方向做了这么多 MySQL 集群的一致性问题排查我最大的体会是不要一开始就追求绝对强一致先量化业务真正能容忍的数据窗口。大部分读写分离业务秒级别延迟可接受那么异步复制 半同步 GTID 校验这套组合已经足够如果业务要求用户写后立即读到自己写的内容那就在应用层把关键请求强制路由到主库而不是指望从库实时追上如果涉及跨机房容灾才需要认真考虑 MGR 甚至异地多活架构。这套东西后续可以扩展的方向还有不少比如对 MGR 做一次分布式故障演练、写一套自动化的主从数据校验流水线、或者把半同步降级事件接入告警平台做实时感知。最后再给一个实用的小建议找两台低配实例每个月定期做一次主从切换演练把切换脚本和数据校验流程跑熟真出故障时团队能少踩一半的坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑