☰
MySQL集群落地:主从复制、高可用切换与踩坑实践
2026/9/28 22:56:09 网站建设 项目流程

MySQL做到集群这一步,基本上等于把数据库从“一个人的单打独斗”变成了“一群人的协作分工”。主从复制、GTID、半同步、读写分离、自动故障切换……随便哪个词拿出来都能聊一下午,但真正让一个集群跑得稳的关键,反而是你有没有想清楚:这套架构到底要解决什么级别的故障、能接受丢多少数据、允许中断多久。这些都是纯技术问题,但没想清楚之前直接上手搭主从,后面八成会还债。

这篇分享是MySQL进阶集群系列的第二篇,默认你已经掌握了单机MySQL的日常运维和基础SQL优化,能看懂binlog、relay log这些基本概念。这次重点讲集群架构在落地过程中真正要命的几个环节:复制机制的选择、读写分离的边界、高可用方案的对比,以及我在生产环境里踩过的一堆坑。适合正在从单机走向主从、或者已经搭了主从但总感觉心里没底的同学。

先说一个我反复强调的观点:集群不是越复杂越好,而是越贴合你的容灾目标越好。下面从需求拆解开始,一步步把集群这摊事捋清楚。

1. 单机扛不住之后的第一个问题:集群到底要解决什么

很多团队搭集群的起因特别朴素:数据库CPU偶尔飙到100%,或者磁盘快满了,又或者半夜主库宕机,业务停了两个小时。于是决定上主从,结果主从搭好之后发现,读写压力还是集中在主库,从库只是多个备份,心里想着“高可用”但真把主库kill掉之后,连怎么切换都不知道。问题出在哪?出在没把需求量化。

我建议动手之前先想清楚三个数:RTO(Recovery Time Objective)允许多久恢复,RPO(Recovery Point Objective)允许丢多少数据,以及目标读QPS是多少。这三个数直接决定你用什么级别的复制方案、要不要上半同步、切换是手动还是自动化。比如你的业务允许丢1秒内的数据、中断5分钟没问题,那异步复制加脚本切换也可以接受;如果一毛钱数据都不能丢,RPO接近零,那半同步复制或者Group Replication基本是必选,而且还得考虑跨机房的网络质量。

1.1 先量化你的痛,再谈架构

我见过最典型的错误,是把“高可用”和“高性能”混在一起谈。主从复制确实能让读QPS水平扩展,但代价是写入仍然只有一个入口,而且从库回放压力会随着从库数量增加而同步上升。换句话说,一主三从解决的是“读多写少”和高可用冗余问题,它解决不了“写瓶颈”。如果你的瓶颈是写入量大、单机写入TPS已经到顶,那需要的不是主从,而是分库分表或者升级硬件。这一步选错,后面怎么调都别扭。

所以开工前可以做个简单的需求收敛表:

痛点集群能解决集群解决不了对应方案
读QPS高、CPU被打满增加从库分摊读写入瓶颈仍在主库一主多从 + 读写分离
主库宕机业务中断从库快速顶替自动切换需额外方案配合半同步 + 自动化切换组件
磁盘容量不足从库无法扩容总容量单库容量瓶颈仍在分库分表或冷热分离
数据安全性要求高半同步减少丢失窗口网络抖动可能影响写入半同步参数调整 + 多副本

把这张表填完,你自然知道你该往哪个方向走。

1.2 三类常见拓扑,按业务阶段对号入座

  • 一主一从:最轻量的高可用结构,主要解决“主库挂了能尽快顶上”的问题,顺带可以做备份源,日常备份从从库上拉,避免备份影响主库。这个结构适合刚开始做容灾的中小项目。
  • 一主多从:在上一级基础上扩展读能力,从库之间是平等的,可以挂不同业务线使用。但要注意从库数量越多,主库的binlog分发压力越大,一般情况下3到5个从库是甜点区,再多就得考虑级联复制。
  • 多主或MGR(Group 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_format=ROW,最好在监控里把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对错一个字节,复制链就可能断掉,数据还无法对齐。而GTID(Global Transaction Identifier)给每个事务分配了一个全局唯一ID,从库只需要记住自己执行到哪个GTID,不用关心文件偏移量。

生产环境建议直接启用GTID模式,关键参数如下:

gtid_mode=ON enforce_gtid_consistency=ON

从5.7开始GTID已经是成熟稳定的功能,不要有心理负担。启用GTID后,主从切换、加新从库都变得简单。比如新加一个从库时,不需要手动去找position,只要从备份或已有从库克隆一份数据,然后执行CHANGE MASTER TO MASTER_AUTO_POSITION=1,从库会自动从主库拉取缺失的GTID事务。这条命令是集群维护里我使用频率最高的一句。

GTID还有一个隐藏的好处:在判断两个实例的数据是否对齐时,直接对比@@GLOBAL.gtid_executed即可。比信心满满地对着File和Position猜领口要可靠得多。

2.4 并行复制配置与延迟的真实来源

从库延迟几乎每个集群都会遇到,最常见的原因是串行回放赶不上主库的写入速度。白白把从库搞成了瓶颈。MySQL 8.0的默认并行复制策略是基于写集合的,它能把互不冲突的事务并行回放,参数配置如下:

slave_parallel_type=LOGICAL_CLOCK slave_parallel_workers=8

slave_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-size=100的由来。

但要注意,这只考虑了数据库侧的连接占用,还没算应用侧线程池排队。连接池太大时,大量连接同时去争抢数据库的锁和CPU,反而会造成更长的等待。连接池太小,活跃线程都卡在获取连接上。我在压测中发现,连接数从50加到100对吞吐提升明显,从100加到200往往收益就很小了。所以不要盲目调大,连接池合理的归宿是配合压测结果和数据库的max_connections一起调整。另外务必给应用设置连接获取超时时间,比如HikariCP的connection-timeout=30000,避免连接池耗尽时请求无限期挂死。

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:老牌但依然能打

MHA(Master High Availability)是很经典的方案,思路不复杂:监控主库健康,确认主库故障后,选一个数据最新的从库,把缺失的binlog从旧主库拉回来,然后提升为新主库,再让其他从库切换复制源。它自己不做VIP或域名切换,一般配合脚本完成这一层。MHA的优势是原理清晰、部署相对轻量,基于原生复制就能跑,很多老项目到现在还在用它。

缺点是它没有真正意义上的“防脑裂”措施,在网络分区场景下可能两边都认为自己活得好好的。另外MHA对GTID的适配一般,需要自己处理很多细节。如果团队人力有限,我不太建议现在再新上MHA,它更适合作为教学组件理解切换原理。

4.2 Orchestrator:拓扑识别更聪明的组件

Orchestrator是另一个很值得关注的工具,它的核心能力是自动发现MySQL拓扑,保存整个复制关系图,并提供Web界面和API。当主库故障时,它可以自动做故障恢复,同时能判断候选从库的数据位置、自动修正复制拓扑。相比MHA,Orchestrator对GTID支持更好,且能在恢复后自动重新挂接从库,运维体验好很多。

使用时通常会把Orchestrator部署在同机房的三个节点上,它自己也是个小集群。它会持续心跳探测MySQL实例,一旦确认主库异常,执行恢复操作。生产实践中把Orchestrator的自动恢复开启后,主从切换时间通常能压到30秒内,前提是你提前配置好候选主库、数据一致性检查开关和恢复后的hook脚本(比如通知、VIP切换)。缺点是要理解它自己的配置体系,以及它和VIP脚本的配合需要自己写。

4.3 官方路线:InnoDB Cluster与Group Replication

MySQL 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连接还指向旧主IP,VIP一飘,已有连接可能不会立刻断开。所以切换的hook脚本里,除了VIP切换,还建议做一次KILL旧主上的连接或者在网络层切流。前段时间我发现一个排障案例:VIP已经切到新主了,但从库状态显示一堆TIME_WAIT连接,业务侧反馈查询偶尔报错,排查下来是连接池重连策略等待时间过长。解决办法是把连接池的connection-test-query和max-lifetime调低。

第三,防脑裂一定要和上层机制联动。单纯依赖数据库层的心跳不一定可靠,在跨机房、网络抖动场景下,两个机房可能同时认为主库在自己这边。我的经验是先配置半同步,让主库在没有从库ack时自动降级或阻塞一定时间,再让上层VIP脚本在切换前通过仲裁节点确认一次“对端机房网络是否可达”,双保险才敢动。

5. 从配置到监控,集群落地要盯住的关键清单

很多主从集群看起来通了,实际配置里到处是隐患。比如有人开着GTID却忘了enforce_gtid_consistency=ON,有人binlog_format还是STATEMENT,有人sync_binlog=0而不自知。下面这套参数模板是我在生产环境验证过的基础版,新搭集群直接照搬,再根据业务调整。

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 = 1

sync_binlog=1和innodb_flush_log_at_trx_commit=1这两项是为了保证每次事务提交时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 = 8

read_only=ON保证客户端不能直接往从库写数据,避免人为导致主从数据不一致。注意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): Can't 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_SSL=0,或者给从库配置好证书和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 event,SQL线程停滞,主从数据出现偏差。当时业务比较要紧,不能等到半夜重建从库,所以现场选择了跳过错误事务恢复复制。跳过事务的方式要区分是否启用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演练,切换后新主库直接开放写流量,结果一对比才发现它少执行了旧主库上的两个事务,差点把数据弄丢。养成这个习惯之后,再复杂的拓扑调整我也敢下手了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询