
MySQL做到集群这一步基本上等于把数据库从“一个人的单打独斗”变成了“一群人的协作分工”。主从复制、GTID、半同步、读写分离、自动故障切换……随便哪个词拿出来都能聊一下午但真正让一个集群跑得稳的关键反而是你有没有想清楚这套架构到底要解决什么级别的故障、能接受丢多少数据、允许中断多久。这些都是纯技术问题但没想清楚之前直接上手搭主从后面八成会还债。这篇分享是MySQL进阶集群系列的第二篇默认你已经掌握了单机MySQL的日常运维和基础SQL优化能看懂binlog、relay log这些基本概念。这次重点讲集群架构在落地过程中真正要命的几个环节复制机制的选择、读写分离的边界、高可用方案的对比以及我在生产环境里踩过的一堆坑。适合正在从单机走向主从、或者已经搭了主从但总感觉心里没底的同学。先说一个我反复强调的观点集群不是越复杂越好而是越贴合你的容灾目标越好。下面从需求拆解开始一步步把集群这摊事捋清楚。1. 单机扛不住之后的第一个问题集群到底要解决什么很多团队搭集群的起因特别朴素数据库CPU偶尔飙到100%或者磁盘快满了又或者半夜主库宕机业务停了两个小时。于是决定上主从结果主从搭好之后发现读写压力还是集中在主库从库只是多个备份心里想着“高可用”但真把主库kill掉之后连怎么切换都不知道。问题出在哪出在没把需求量化。我建议动手之前先想清楚三个数RTORecovery Time Objective允许多久恢复RPORecovery Point Objective允许丢多少数据以及目标读QPS是多少。这三个数直接决定你用什么级别的复制方案、要不要上半同步、切换是手动还是自动化。比如你的业务允许丢1秒内的数据、中断5分钟没问题那异步复制加脚本切换也可以接受如果一毛钱数据都不能丢RPO接近零那半同步复制或者Group Replication基本是必选而且还得考虑跨机房的网络质量。1.1 先量化你的痛再谈架构我见过最典型的错误是把“高可用”和“高性能”混在一起谈。主从复制确实能让读QPS水平扩展但代价是写入仍然只有一个入口而且从库回放压力会随着从库数量增加而同步上升。换句话说一主三从解决的是“读多写少”和高可用冗余问题它解决不了“写瓶颈”。如果你的瓶颈是写入量大、单机写入TPS已经到顶那需要的不是主从而是分库分表或者升级硬件。这一步选错后面怎么调都别扭。所以开工前可以做个简单的需求收敛表痛点集群能解决集群解决不了对应方案读QPS高、CPU被打满增加从库分摊读写入瓶颈仍在主库一主多从 读写分离主库宕机业务中断从库快速顶替自动切换需额外方案配合半同步 自动化切换组件磁盘容量不足从库无法扩容总容量单库容量瓶颈仍在分库分表或冷热分离数据安全性要求高半同步减少丢失窗口网络抖动可能影响写入半同步参数调整 多副本把这张表填完你自然知道你该往哪个方向走。1.2 三类常见拓扑按业务阶段对号入座一主一从最轻量的高可用结构主要解决“主库挂了能尽快顶上”的问题顺带可以做备份源日常备份从从库上拉避免备份影响主库。这个结构适合刚开始做容灾的中小项目。一主多从在上一级基础上扩展读能力从库之间是平等的可以挂不同业务线使用。但要注意从库数量越多主库的binlog分发压力越大一般情况下3到5个从库是甜点区再多就得考虑级联复制。多主或MGRGroup Replication严格说这是为了解决“自动故障恢复”和“多点写入”问题的方案但MGR的多写模式对冲突检测要求很高热点行更新场景下性能并不好看。我更建议把多写当作一个可用性手段而不是性能扩展手段。另外一个容易忽略的维度机房拓扑。跨机房部署时主从之间的网络延迟会被无限放大半同步复制会直接把主库写入拖慢。我的做法是同机房内用半同步跨机房采用异步再配合独立的高可用切换组件来兜底。这条经验后面讲切换方案时还会展开。2. 复制是集群的地基binlog与GTID是地基里的钢筋主从复制看起来就是“主库把binlog发给从库从库执行”但真正落地时牵涉到三个线程的协作、binlog格式选择、半同步参数、GTID开启方式。这块如果只是照抄配置文件出问题的时候你连排查入口都找不到。2.1 复制链路拆解三个线程各干各的一条复制链路里三个线程分工非常清晰主库上的binlog dump线程负责读取binlog并发送给从库。从库上的IO线程负责接收主库发来的binlog写到本地的relay log中。从库上的SQL线程负责读取relay log并在从库上回放。mysql 8.0之后的SQL线程已经支持多线程并行回放但IO线程仍然只有一个它只负责写relay log通常不是瓶颈。排查延迟时第一步就要分清是IO线程慢了还是SQL线程慢了。看SHOW SLAVE STATUS\G里的Slave_IO_Running和Slave_SQL_Running这两个状态以及Seconds_Behind_Master再顺着Last_IO_Errno、Last_SQL_Errno往下查。很多新手上来就盯着Seconds_Behind_Master但它是个估值尤其是并行复制开启后这个值并不精确。后面第六章我会给实际案例。binlog格式建议直接用ROW。虽然ROW格式的binlog体积比STATEMENT大不少但它能保证回放结果的确定性。STATEMENT格式在遇到UUID()、NOW()、RAND()这类非确定性函数时从库回放结果可能和主库不一致。还有那些用了触发器、存储过程做复杂写入的业务STATEMENT格式的复制风险更大。一个经常被忽略的点binlog_format是会话级参数你可以在某个会话里把它改成STATEMENT如果这个会话执行了非确定性更新操作主从数据就悄悄偏了。所以生产环境不仅要在配置文件里写死binlog_formatROW最好在监控里把binlog_format的非ROW会话也盯上。2.2 半同步复制用一点写入延迟换取接近零丢失异步复制的最大问题在于主库提交事务成功但binlog还没发到从库此时主库宕机这部分数据就丢了。半同步复制正是为解决这个问题出现的。它的核心逻辑是主库提交事务后必须等待至少一个从库确认收到binlog才返回客户端成功。MySQL原生半同步有两个关键参数rpl_semi_sync_master_timeout和rpl_semi_sync_master_wait_point。前者表示等待超时时间默认10秒超过时间主库会自动降级为异步保证写入不永久阻塞后者有两个值AFTER_COMMIT和AFTER_SYNC。强烈建议用AFTER_SYNC主库把binlog落盘、发给从库并收到ack之后才进入commit阶段。这样主库宕机时已经提交的事务至少存在于一个从库上RPO接近零。AFTER_COMMIT是在commit之后才等ack如果等ack期间主库宕机客户端可能收到失败但事务实际已提交就会很尴尬。在MySQL 8.0里半同步插件直接内嵌启用方式非常简单INSTALL PLUGIN rpl_semi_sync_source SONAME semisync_source.so; INSTALL PLUGIN rpl_semi_sync_replica SONAME semisync_replica.so; SET GLOBAL rpl_semi_sync_source_enabled 1; SET GLOBAL rpl_semi_sync_replica_enabled 1;注意从库也要开插件但不需要开source端参数主库需要开source端参数。配置完用SHOW PLUGINS验证插件状态再检查Rpl_semi_sync_source_status和Rpl_semi_sync_source_clients确保确实处于半同步工作状态。我遇到过不少情况插件装好了、参数也设了但实际半同步根本没生效因为主库的rpl_semi_sync_source_enabled只在配置文件中设了而没注意动态修改时机的先后顺序或者从库binlog没开导致无法ack。半同步的完整链路要求从库必须开启binlog否则它是无法正常反馈的。2.3 GTID的价值给failover装上了GPS传统主从切换时你得找准主库当前binlog文件名和position再让新主库从这个位置开始拉取旧主的binlog。只要position对错一个字节复制链就可能断掉数据还无法对齐。而GTIDGlobal Transaction Identifier给每个事务分配了一个全局唯一ID从库只需要记住自己执行到哪个GTID不用关心文件偏移量。生产环境建议直接启用GTID模式关键参数如下gtid_modeON enforce_gtid_consistencyON从5.7开始GTID已经是成熟稳定的功能不要有心理负担。启用GTID后主从切换、加新从库都变得简单。比如新加一个从库时不需要手动去找position只要从备份或已有从库克隆一份数据然后执行CHANGE MASTER TO MASTER_AUTO_POSITION1从库会自动从主库拉取缺失的GTID事务。这条命令是集群维护里我使用频率最高的一句。GTID还有一个隐藏的好处在判断两个实例的数据是否对齐时直接对比GLOBAL.gtid_executed即可。比信心满满地对着File和Position猜领口要可靠得多。2.4 并行复制配置与延迟的真实来源从库延迟几乎每个集群都会遇到最常见的原因是串行回放赶不上主库的写入速度。白白把从库搞成了瓶颈。MySQL 8.0的默认并行复制策略是基于写集合的它能把互不冲突的事务并行回放参数配置如下slave_parallel_typeLOGICAL_CLOCK slave_parallel_workers8slave_parallel_workers不是越大越好。我见过有人直接设成64结果从库CPU没事但线程调度和锁等待的开销反而上去了。更合理的做法是从4开始压测观察从库回放速率与CPU使用率逐步提升。在我的实践中绝大多数负载8到16个并发回放线程已经能追平主库。延迟的另一个来源是大事务。一次UPDATE影响几百万行主库跑了两分钟从库回放同样要跑两分钟这期间其他事务全部排队。这类问题不是调并行复制能解决的最有效的方法是拆分事务或者用在线DDL工具解决结构性变更的延迟。另外主从硬件差异过大会从根源上限制回放速度生产环境从库的CPU和磁盘配置不应该比主库差太多。3. 读写分离落地真正的难点在连接池和事务边界主从搭好了业务怎么把读流量导到从库这是集群实践里最容易被低估的一步。很多团队直接在每个业务里写两套数据源写走主库、读走从库看起来简单但很快就会发现事务里读到旧数据、刚写的数据查不到、连接池把事务连接和普通查询连接混在一起。这些问题的根源都在于没有想清楚读写分离的事务边界。3.1 在等待中间件之前先用驱动级读写分离顶住引入MyCat、ShardingSphere这类中间件是个不小的工程路由规则、SQL解析、分布式事务都得折腾一遍。如果你的阶段只是“想把读流量分摊到从库”完全可以从驱动级做起。MySQL官方Connector/J从5.1版本开始就支持replicationConnection它能自动把connection.setReadOnly(true)的请求路由到从库普通连接走主库。Java侧的配置思路大致是这样提供一个基于ReplicationDriver的DataSource配置url时同时指定主库和从库地址然后通过Spring的事务管理把事务标记为只读。核心效果就是在事务中执行查询前先setReadOnly(true)如果你用Spring的Transactional(readOnly true)注解正好能匹配这个机制。这样业务代码改动量很小读流量自然分流。Python侧同理mysql-connector-python也可以自己封装一个连接包装类根据当前是不是只读事务决定走哪个连接。不过我建议这类驱动级方案只作为过渡当从库数量增长到两三个以上、业务线变多之后统一接入代理层或数据访问框架否则每个业务线都做一遍路由逻辑维护成本会非常高。3.2 事务边界哪些请求必须钉死在主库读写分离最容易翻车的场景不是SQL写的烂而是路由把不该走从库的请求分到了从库。我这里总结了几类必须强制走主库的情况事务内既有写又有读一旦事务执行过写操作后面所有的读都必须留在主库的同一个连接上否则就根本不在同一个事务里。刚写入后立刻查询用户提交订单后马上跳转到订单详情页面此时主从延迟可能还没结束详情页查询走了从库就会看到“订单不存在”这是最典型的线上事故。带FOR UPDATE或LOCK IN SHARE MODE的查询这类锁语义必须和写入数据的主库保持一致走从库没有任何锁保护意义。强一致性要求的接口查询比如余额、库存这类业务方案上可以直接在接口层标记一个“必须走主库”开关宁可多压一点主库读也不能让用户看到不一致的数据。这条边界需要在连接层而不是SQL层去做。靠开发人员自觉“注意一下”是不可靠的人总会忘一忘就是线上事故。务必要在连接池层面把“只读事务”和“读写事务”从物理连接上分离。3.3 连接池大小不是拍脑袋定的连接池参数的坑我见得太多了。有人maximum-pool-size设成500数据库连接数直接被打爆有人设成5一上流量就排队超时。连接池大小的估算逻辑其实很简单一个连接的吞吐能力取决于单个事务的平均耗时如果你目标QPS是2000平均每个事务耗50毫秒那么需要的并发连接数大约是2000 * 0.05 100。这就是HikariCP里maximum-pool-size100的由来。但要注意这只考虑了数据库侧的连接占用还没算应用侧线程池排队。连接池太大时大量连接同时去争抢数据库的锁和CPU反而会造成更长的等待。连接池太小活跃线程都卡在获取连接上。我在压测中发现连接数从50加到100对吞吐提升明显从100加到200往往收益就很小了。所以不要盲目调大连接池合理的归宿是配合压测结果和数据库的max_connections一起调整。另外务必给应用设置连接获取超时时间比如HikariCP的connection-timeout30000避免连接池耗尽时请求无限期挂死。3.4 集群下的事务、锁与死锁排查主从模式下锁的问题虽然不会因为复制而放大但排查链路会变长。比如主库上有个事务长时间不提交持有行锁binlog迟迟不刷所有复制线程都在等这个事务完成。你从SHOW PROCESSLIST上看从库可能没有锁等待但主库的事务一直没结束从库的relay log就一直堆积Seconds_Behind_Master持续上升。这种因为主库长事务导致的从库延迟很多人会误判成从库性能问题。排查锁等待我习惯先看三张视图information_schema.INNODB_TRX看事务状态sys.INNODB_LOCK_WAITS看锁等待关系performance_schema.events_statements_current看当前正在执行的语句。找到持锁事务的trx_started时间再反查这个事务对应的应用连接。通常就是某个接口里忘了提交事务或者事务内混了外部RPC调用导致事务生命周期被拉长到秒级甚至分钟级。顺带提醒间隙锁Gap Lock在可重复读隔离级别下很容易引发死锁而复制模式下从库回放时也要获取同样的锁。如果你在主库上绕过索引做大范围更新从库回放时就会锁住更大范围极端情况下从库回放线程之间互相死锁。对这种问题光靠SHOW ENGINE INNODB STATUS里的死锁信息还不够真正有用的动作是优化SQL走索引把锁范围降下去。4. 高可用切换方案选型别让“手动切”背RTO的锅主从复制本身不做故障切换。主库挂了从库还在那等着业务照样连不上。很多团队停留在“手动切换”阶段发现主库宕机人工登录从库执行CHANGE MASTER、改VIP或改应用配置。这一套下来哪怕操作熟练十分钟到半小时的RTO也跑不掉。如果你的业务能接受这种恢复速度那就保持现状但多数线上业务受不了所以需要一个自动切换组件。4.1 MHA老牌但依然能打MHAMaster High Availability是很经典的方案思路不复杂监控主库健康确认主库故障后选一个数据最新的从库把缺失的binlog从旧主库拉回来然后提升为新主库再让其他从库切换复制源。它自己不做VIP或域名切换一般配合脚本完成这一层。MHA的优势是原理清晰、部署相对轻量基于原生复制就能跑很多老项目到现在还在用它。缺点是它没有真正意义上的“防脑裂”措施在网络分区场景下可能两边都认为自己活得好好的。另外MHA对GTID的适配一般需要自己处理很多细节。如果团队人力有限我不太建议现在再新上MHA它更适合作为教学组件理解切换原理。4.2 Orchestrator拓扑识别更聪明的组件Orchestrator是另一个很值得关注的工具它的核心能力是自动发现MySQL拓扑保存整个复制关系图并提供Web界面和API。当主库故障时它可以自动做故障恢复同时能判断候选从库的数据位置、自动修正复制拓扑。相比MHAOrchestrator对GTID支持更好且能在恢复后自动重新挂接从库运维体验好很多。使用时通常会把Orchestrator部署在同机房的三个节点上它自己也是个小集群。它会持续心跳探测MySQL实例一旦确认主库异常执行恢复操作。生产实践中把Orchestrator的自动恢复开启后主从切换时间通常能压到30秒内前提是你提前配置好候选主库、数据一致性检查开关和恢复后的hook脚本比如通知、VIP切换。缺点是要理解它自己的配置体系以及它和VIP脚本的配合需要自己写。4.3 官方路线InnoDB Cluster与Group ReplicationMySQL 8.0官方主推的是InnoDB Cluster底层是Group Replication组复制配合MySQL Router做流量路由。相比MHA和Orchestrator这是从数据库内核层面解决的方案组内多个节点通过Paxos协议通信自动选举主节点故障时自动切换。多写模式下每个节点都能写入但冲突检测成本高所以我还是建议单主模式把组复制当成更可靠的高可用机制。InnoDB Cluster的优势体现在“官方支持”和“配置集成度”上。用mysqlsh创建集群之后它会自动配置复制账号、组通信端口、成员诊断等。MySQL Router则把写流量固定路由到主节点读流量可以分散到所有节点。这套方案在9.0等新版本中已经相当成熟许多云数据库厂商的高可用底层走的就是类似思路。不过它也不是银弹。Group Replication对网络质量要求比较高节点间延迟最好在5毫秒以内跨机房部署容易出问题同时所有节点都要开启GTID并且配置严格一致性。如果你还在用5.7甚至更老的版本迁移成本也不小。下面这个表格是我给团队选型时的对比口径方案自动切换数据一致性依赖组件适用场景手动切换脚本否取决于操作者自定义脚本RTO允许10分钟以上MHA是中依赖旧主binlog补拉MHA组件 VIP脚本熟悉原生复制的存量环境Orchestrator是中高GTID支持好Orchestrator 脚本大型复杂拓扑的自动化管理InnoDB Cluster是高组内共识MySQL Router Shell新项目、对官方能力信任度高4.4 切换细节里的三个致命点第一旧主恢复后能不能自动回集群不管是MHA还是Orchestrator切换后旧主重新加入集群前必须把它和当前主库的差距追平否则会出现数据回滚或重复。GTID模式下旧主会被要求清掉不在主库GTID集合里的事务这一步务必确认清楚否则可能把旧主上孤悬的数据暴露给新业务。第二VIP切换和连接池的关系。应用连接池里缓存的TCP连接还指向旧主IPVIP一飘已有连接可能不会立刻断开。所以切换的hook脚本里除了VIP切换还建议做一次KILL旧主上的连接或者在网络层切流。前段时间我发现一个排障案例VIP已经切到新主了但从库状态显示一堆TIME_WAIT连接业务侧反馈查询偶尔报错排查下来是连接池重连策略等待时间过长。解决办法是把连接池的connection-test-query和max-lifetime调低。第三防脑裂一定要和上层机制联动。单纯依赖数据库层的心跳不一定可靠在跨机房、网络抖动场景下两个机房可能同时认为主库在自己这边。我的经验是先配置半同步让主库在没有从库ack时自动降级或阻塞一定时间再让上层VIP脚本在切换前通过仲裁节点确认一次“对端机房网络是否可达”双保险才敢动。5. 从配置到监控集群落地要盯住的关键清单很多主从集群看起来通了实际配置里到处是隐患。比如有人开着GTID却忘了enforce_gtid_consistencyON有人binlog_format还是STATEMENT有人sync_binlog0而不自知。下面这套参数模板是我在生产环境验证过的基础版新搭集群直接照搬再根据业务调整。5.1 一套经过生产验证的基础参数模板主库和从库都需要配置的基础项[mysqld] server_id 100 log_bin mysql-bin binlog_format ROW gtid_mode ON enforce_gtid_consistency ON binlog_rows_query_log_events ON sync_binlog 1 innodb_flush_log_at_trx_commit 1sync_binlog1和innodb_flush_log_at_trx_commit1这两项是为了保证每次事务提交时binlog和InnoDB redo log都持久化落盘是数据安全的基础。它们的代价是每次提交多两次fsync如果你用SSD基本无感机械盘场景下写入会明显变慢。这是典型的可用性大于性能的决定。从库额外配置relay_log mysql-relay-bin log_slave_updates ON read_only ON slave_parallel_type LOGICAL_CLOCK slave_parallel_workers 8read_onlyON保证客户端不能直接往从库写数据避免人为导致主从数据不一致。注意read_only对SUPER权限的开发账号不生效所以要从账号体系上保证开发账号没有SUPER权限这个坑我在线下运维时踩过不止一次。半同步的启用前面已经写过这里就不再重复命令了。有一个细节必须提醒半同步开启后如果从库长期未ack主库长时间处于等待状态导致应用写入变慢先检查rpl_semi_sync_master_timeout的配置。生产上建议设置成一个可接受的阈值比如3000毫秒超过后自动降级为异步保证写入不被拖死。5.2 连接数是性能调优的第一道坎第3章已经给了连接池大小的估算逻辑这里补充一个数据库侧的视图化排查法。MySQL 8.0下用performance_schema的status_by_thread视图就能实时看到每个线程的状态如果频繁出现Waiting for table metadata lock说明有大查询或DDL卡住了元数据锁如果大量线程处于Statistics状态往往是SQL优化问题而不是连接池问题。对于开发阶段的性能调优我的建议是先盯四个指标当前活跃连接数、Com_select与Com_insert/update/delete的比值、锁等待次数、主从延迟。这四个指标能覆盖集群性能调优80%的方向。活跃连接数突增且大量处于Query状态优先排查慢SQL和全表扫描Com_select远高于其他语句但CPU又不高检查是否该走从库的查询走到了主库锁等待次数突增优先看长事务和间隙锁。5.3 监控指标要少而准搭建集群监控时我建议你少加指标多添告警。指标加多了最终只会被忽略。我最先加的一批监控是主从复制状态Slave_IO_Running、Slave_SQL_Running是否都为Yes任一变成No立刻告警。Seconds_Behind_Master这个值虽然不精确但超过阈值仍值得告警。正常情况应该长时间为0持续增长说明从库追不上。半同步状态从库是否处于半同步工作中主库是否出现过降级为异步的情况。磁盘和binlog增长binlog增长速率异常往往是大事务或刷日志操作出现问题。主库活跃事务数超过阈值说明有长事务在拖累性能。如果是Docker或Kubernetes里部署的MySQL集群监控还得加上容器重启次数和持久化存储使用率。容器化部署主从有一个特有的问题多个实例如果复用了同一个数据目录或者镜像里自带var目录server_uuid会相同直接导致复制链路建立失败。这个问题在第六章展开讲。6. 生产环境踩坑记录从报错到定位的完整链路这部分内容来自我自己的排障笔记每一个案例都是查完资料、做了复盘之后沉淀下来的。希望你看的时候不只是记答案而是跟着排查思路走一遍。6.1 ERROR 2002与主从SSL连接失败很多人在主从复制配置完成后发现IO线程报错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。这个报错通常不是SQL层面的问题而是连接MySQL实例时没有指定正确的socket路径。排查思路很简单先确认MySQL监听的socket文件位置用mysql -S /path/to/mysql.sock -u xx -p测试本地连接如果这个命令能通说明你的客户端工具配置里socket路径不对。而在主从复制场景更常见的是下面这个报错ERROR 2026 (HY000): SSL connection error。主库启用了SSL强制校验但从库连接参数没有关闭SSL或者证书配置不对。MySQL 8.0默认开启了SSL支持如果你在CHANGE MASTER TO语句里不写SSL相关选项可能走默认SSL连接导致证书校验失败。解决办法是在CHANGE MASTER TO时显式加上MASTER_SSL0或者给从库配置好证书和MASTER_SSL_CA参数。生产环境如果机房内网相对可信我一般直接关闭主从之间的SSL加密减少证书分发和轮换的负担如果跨机房必须走公网再考虑启用SSL但证书生命周期管理一定要自动化。6.2 容器化部署MySQL集群server_uuid冲突容器部署MySQL主从时最容易踩的坑是我上面提过的server_uuid冲突。表现是IO线程反复连接失败日志里报Fatal error: The slave I/O thread stops because the master and the slave have equal MySQL server UUIDs。原因很简单很多人用同一个镜像副本初始化数据目录或者从已有从库的备份直接恢复到新容器但没清掉auto.cnf。SQL线程根本没办法处理一个和自己UUID相同的主库。修复方式也很直接登录从库容器停止MySQL服务删除datadir/auto.cnf重新启动MySQL。服务启动时会自动生成新的UUID。注意删除前先检查一下这个从库是不是已经有复制在跑如果已经有复制链路上半截在用修完UUID后要用STOP SLAVE、CHANGE MASTER TO重新指定一下连接信息再START SLAVE恢复。另外在Kubernetes里如果使用StatefulSet务必给每个实例挂独立PV不要共享数据卷否则auto.cnf和binlog都可能互相覆盖。6.3 半同步参数惹的祸主库写入被拖垮有一次线上主库写入突然变慢单条UPDATE从毫秒变成秒级业务方反馈越来越严重。我登录主库一查发现Rpl_semi_sync_source_status为YES但大量事务卡在Waiting for semi-sync ACK from slave状态。再查从库发现从库的IO线程是正常的但relay log同步到磁盘的速度很慢。原因是当时从库所在的机器磁盘性能低下导致ack反馈延迟很大。半同步主库就这么一直干等把写入全堵住了。那个案例最终没有去优化从库磁盘因为硬件一时半会替换不了而是临时把rpl_semi_sync_master_timeout从默认的10秒调小到3000毫秒触发半同步降级为异步先恢复主库写入。随后联系硬件团队给从库换盘确认从库能稳定ack后再重新开启半同步。这个案例给我的教训有二一是半同步必须配超时降级绝不能无限等ack二是从库的磁盘性能对主库影响是实打实的不要觉得从库嘛配置差一点没关系。6.4 数据不一致怎么抢救GTID修复实操还有一次一个从库因为索引不一致导致回放报错Error executing row eventSQL线程停滞主从数据出现偏差。当时业务比较要紧不能等到半夜重建从库所以现场选择了跳过错误事务恢复复制。跳过事务的方式要区分是否启用GTID。如果没启用GTID可以用SET GLOBAL sql_slave_skip_counter 1;但需要先STOP SLAVE执行后START SLAVE。注意一次跳一个事务如果一个事务里有多个错误事件可能要重复操作。GTID模式下操作更推荐这样STOP SLAVE; SET GTID_NEXT 报错事务的GTID; BEGIN; COMMIT; SET GTID_NEXT AUTOMATIC; START SLAVE;这相当于手动把报错事务的空事务提交到GTID执行集合里让SQL线程跳过它。但跳事务只是止血它意味着这个事务在主从上的执行结果已经不一致了必须标记出来事后对账或重建这个从库。我当时跳完事务后第一时间给这个从库打标记录影响的时间窗口然后在业务低峰期用pt-table-checksum和pt-table-sync完成了数据对账和修复。这里还想提醒一点如果从库回放错误的根因是“从库上被人手动改过数据”那即使跳过了事务后续回放也可能继续报错。因为主库更新的那行数据和从库手动改过的数据冲突。所以从库坚决开启read_only并严格控制账号权限才是防止这类问题复发的根本手段。最后再分享一个我个人的小习惯每次做任何主从拓扑变更之前先把目标实例的GLOBAL.gtid_executed备份出来变更之后和预期结果对比。这句话值多少钱呢有一次我在测试环境做failover演练切换后新主库直接开放写流量结果一对比才发现它少执行了旧主库上的两个事务差点把数据弄丢。养成这个习惯之后再复杂的拓扑调整我也敢下手了。