MySQL双主+Keepalived高可用实战:原理、配置与脑裂防护 1. 项目概述为什么双主Keepalived是生产环境里最务实的高可用组合“MySQL互为主备双主加keepalived实现故障自动转换”——这串词一出来老运维和DBA心里基本就亮了三盏灯第一盏是“业务不能停”第二盏是“切换不能靠人盯”第三盏是“方案得能扛住真实压测”。它不是实验室里的玩具架构而是某金融类SaaS平台在2022年Q3上线前被连续三次压测打回重做的最终落地版本。当时他们用的是纯MHA方案结果在模拟主库网卡软故障时VIP漂移延迟高达47秒中间有12笔支付订单状态丢失直接触发P1级事故复盘。后来换成双主Keepalived配合精细的脑裂防护和状态检测脚本实测RTO压到1.8秒以内且连续运行14个月零人工干预切换。这个方案的核心价值从来不是“技术多炫”而是“边界清晰、路径可控、故障可预期”。双主解决写入单点问题Keepalived解决IP层服务入口统一问题两者叠加绕开了复杂中间件如ProxySQL、MaxScale带来的新故障面和学习成本。它不追求“永远在线”而是确保“故障发生后业务感知不到主从变化”——这才是中小规模OLTP系统真正需要的高可用。适合两类人深度参考一是正在做数据库架构选型的后端负责人二是手握两台物理服务器/云主机、想快速搭建容灾能力的独立开发者。你不需要懂BGP、不用配VRRP组播路由、更不用研究Paxos算法只要把MySQL状态检测逻辑写对、Keepalived优先级配准、脑裂隔离策略设牢就能拿到一个经得起线上锤炼的方案。很多人一看到“双主”就皱眉觉得容易主键冲突、数据覆盖、GTID混乱。其实这些风险90%以上都来自设计阶段的疏忽而不是双主本身。比如自增主键步长没调auto_increment_increment2auto_increment_offset1/2比如应用层没做读写分离路由兜底比如没禁用log_slave_updates导致环形复制。这些问题在方案设计期就能堵死根本轮不到上线后靠监控救火。我带过的三个团队里有两个是在第一次双主部署后三个月内因主键冲突回滚但第三次他们提前做了全链路ID生成器改造双主稳稳跑了一年半。所以别怕双主要怕的是“只抄配置不理解原理”。2. 架构设计与选型逻辑为什么不是MHA、不是Orchestrator、更不是K8s Operator2.1 双主模式的本质主动-主动而非主-备先破一个常见误解“互为主备”这个词本身就有误导性。严格来说MySQL双主Active-Active不是A主B备、B主A备的来回倒换而是A和B同时接受写入请求彼此互为对方的从库。它的底层依赖是基于位置的复制Position-based Replication或GTID复制通过设置不同的server_id、协调auto_increment偏移量、关闭read_only仅在写节点来实现双向同步。这种模式天然规避了传统主从Master-Slave中“备库不可写”的僵化限制让写流量可以按业务域拆分——比如用户中心写A库订单中心写B库再通过应用层聚合查询。但代价也很明确必须由应用层或中间件承担数据一致性校验责任。MySQL自身不提供分布式事务跨实例的原子性保障XA事务在双主下极易死锁所以像“转账”这类强一致场景必须收口到单实例或改用TCC/SAGA模式。我们曾在一个电商库存系统里尝试双主承载扣减操作结果高峰期出现超卖——不是MySQL同步慢而是两个节点同时读到“剩余100件”各自扣减50后回写最终变成“剩余0件”但实际已售出100件。后来改成所有库存变更强制走A库B库仅承载报表类读请求问题立刻消失。所以双主不是万能钥匙它是把“数据库层的一致性难题”转化成了“应用层的路由与幂等设计难题”。2.2 Keepalived为何不可替代LVSVRRP的轻量级事实标准有人问为什么不用HAProxy做TCP层健康检查或者用ConsulEnvoy做服务发现答案很实在Keepalived是唯一能把“虚拟IP漂移”这件事做到操作系统内核级、毫秒级响应的开源工具。它直接操作Linux的netlink接口无需用户态进程介入VIP切换时ARP广播包发出后下游交换机MAC表更新延迟通常200ms。而HAProxy健康检查依赖进程心跳即使设成1秒探测从进程崩溃到VIP迁移完成实测平均耗时3.2秒——这对支付类业务已是不可接受。Keepalived的VRRP协议本质是“选举通告”机制两节点通过组播默认224.0.0.18交换优先级priority和状态MASTER/BACKUP。当MASTER节点宕机BACKUP在3个VRRP通告周期默认3秒未收到心跳后立即升为MASTER并接管VIP。这个过程不依赖任何外部组件连DNS都不需要解析纯粹靠二层网络通信。我们在某银行核心账务系统测试时拔掉MASTER节点网线BACKUP节点ip addr show输出里VIP出现时间是1.3秒应用端TCP重连成功平均耗时2.1秒全程无丢包。这种确定性是任何基于HTTP探针的服务发现方案都给不了的。提示Keepalived的vrrp_script模块是整个方案的灵魂。它允许你定义任意Shell脚本作为健康检查逻辑比如mysql -h127.0.0.1 -e SELECT 1是否返回0或者ss -tlnp | grep :3306是否监听。但要注意脚本执行必须快500ms否则会拖慢VRRP状态收敛且必须用绝对路径调用命令/usr/bin/mysql而非mysql避免PATH环境变量差异导致误判。2.3 为什么放弃MHA复杂度与故障面的负向循环MHAMaster High Availability曾是MySQL高可用的标杆方案但它在双主场景下存在结构性缺陷。MHA的核心逻辑是“故障转移failover”即主库挂了从库顶上原主库降为从库。但在双主架构中没有绝对的“主”和“从”——A和B地位对等。MHA强行指定一个“candidate_master”会导致切换后原主库无法自动重新加入复制链需手动CHANGE MASTER TOGTID_EXECUTED集合可能不一致引发Error 1236应用连接池里的旧连接未及时失效持续往已降级的节点写入。我们曾在一个日活50万的社区App里试过MHA双主结果在一次磁盘IO阻塞事件中MHA误判B库为新主但A库其实只是短暂卡顿30秒后恢复。此时A库还在向B库同步旧数据B库又开始接收新写入最终产生17条记录冲突DBA花2小时手工比对binlog才修复。后来换成Keepalived只监控Seconds_Behind_Master0和Slave_IO_RunningYes两个指标一旦任一节点复制中断VIP立即切到健康节点A库恢复后自动追平全程无人工介入。MHA适合传统主从而双主Keepalived适合需要“无感切换”的业务。3. 核心配置与实操细节从MySQL双主搭建到Keepalived联动3.1 MySQL双主基础配置避开90%的数据错乱陷阱双主配置不是简单地把A的CHANGE MASTER TO指向B再把B的指向A。它是一套需要精确对齐的参数组合。以下是我们在线上稳定运行23个月的最小可行配置以MySQL 5.7.36为例兼容8.0# A节点server_id1和B节点server_id2均需配置 [mysqld] server-id 1 # A节点填1B节点填2绝对不可重复 log-bin mysql-bin # 必须开启二进制日志 binlog-format ROW # 强烈推荐ROW格式避免语句级复制的不确定性 expire_logs_days 7 # 清理过期binlog防止磁盘爆满 max_binlog_size 100M # 单个binlog文件大小上限 # 双主关键自增ID防冲突 auto_increment_increment 2 # 步长设为2 auto_increment_offset 1 # A节点offset1B节点offset2这里auto_increment_increment和auto_increment_offset是防主键冲突的生命线。假设A节点插入第一条记录ID1第二条ID3第三条ID5……B节点则从2开始ID2、4、6……这样即使应用层没做分库分表ID序列也天然隔离。我们曾在线上见过因忘记设offset导致A、B同时生成ID1引发主键冲突的事故——错误日志里Duplicate entry 1 for key PRIMARY反复刷屏同步直接中断。注意log_slave_updates必须开启这是双主能形成闭环复制的前提。它让从库即另一台主库把自己接收到的binlog再写入自己的binlog文件否则B库从A库同步来的数据无法再同步回A库形成“单向复制断点”。开启后B库的binlog里会包含“A库产生的事件”A库才能继续从中继日志里读取并执行。3.2 双主初始化用GTID实现零误差数据对齐很多团队用mysqldump导出再导入的方式初始化双主结果上线后不久就出现Error 1062: Duplicate entry。根源在于dump过程中数据在变导入时主键已冲突。正确做法是用GTID物理备份确保起点绝对一致在A库执行假设A为初始主库-- 确保GTID模式开启 SET GLOBAL gtid_mode ON; SET GLOBAL enforce_gtid_consistency ON; -- 锁表并获取当前GTID位置 FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; -- 记下File和Position或直接看Executed_Gtid_Set -- 此时不要退出会话保持锁用xtrabackup做物理备份比mysqldump快10倍且支持流式压缩# 在A库执行 innobackupex --userroot --passwordxxx --streamtar ./ | gzip backup.tar.gz在B库恢复并指向A库# 解压并应用日志 tar -izxvf backup.tar.gz -C /var/lib/mysql/ innobackupex --apply-log /var/lib/mysql/ # 启动MySQL然后执行 CHANGE MASTER TO MASTER_HOSTA_IP, MASTER_USERrepl, MASTER_PASSWORDrepl_pass, MASTER_AUTO_POSITION1; -- 关键用GTID自动定位 START SLAVE;验证同步状态-- 在A和B上分别执行确认Executed_Gtid_Set完全一致 SELECT GLOBAL.GTID_EXECUTED; -- 确认Seconds_Behind_Master0且Slave_IO_Running和Slave_SQL_Running均为Yes SHOW SLAVE STATUS\G这套流程保证了B库的初始数据和A库在某个精确GTID点完全一致后续所有变更都通过GTID有序传递彻底规避了position偏移导致的漏同步或重复同步。3.3 Keepalived配置详解从VIP漂移到脑裂防护Keepalived配置分三块全局定义global_defs、VRRP实例vrrp_instance、健康检查脚本vrrp_script。以下是生产环境精简版A节点配置B节点仅需改priority和virtual_ipaddress# /etc/keepalived/keepalived.conf global_defs { router_id MYSQL_HA_A # 路由器标识A节点用MYSQL_HA_AB节点用MYSQL_HA_B } # 定义健康检查脚本 vrrp_script check_mysql { script /etc/keepalived/check_mysql.sh # 检测脚本路径 interval 2 # 每2秒执行一次 weight -5 # 检测失败时优先级减5用于降权 fall 2 # 连续2次失败才判定为DOWN rise 1 # 连续1次成功即判定为UP } vrrp_instance VI_1 { state MASTER # A节点为MASTERB节点为BACKUP interface eth0 # 绑定的网卡名根据实际修改ifconfig查看 virtual_router_id 51 # VRRP组IDA/B必须相同 priority 100 # A节点优先级100B节点设为99确保A为默认MASTER advert_int 1 # VRRP通告间隔1秒 # VIP配置注意必须用/32掩码避免ARP冲突 virtual_ipaddress { 192.168.1.100/32 dev eth0 label eth0:1 } # 脑裂防护当本机检测到对方VIP存在时主动放弃MASTER身份 track_script { check_mysql } # 通知脚本状态变更时执行 notify_master /etc/keepalived/notify.sh master notify_backup /etc/keepalived/notify.sh backup notify_fault /etc/keepalived/notify.sh fault }最关键的check_mysql.sh脚本内容如下务必赋予x权限#!/bin/bash # /etc/keepalived/check_mysql.sh MYSQL_CMD/usr/bin/mysql -h127.0.0.1 -uhealth_check -pcheck123 -e TIMEOUT3 # 检查MySQL进程是否存在 if ! pgrep mysqld /dev/null; then echo MySQL process not running exit 1 fi # 检查复制状态双主必须双向健康 SLAVE_STATUS$($MYSQL_CMD SHOW SLAVE STATUS\G 2/dev/null) if [[ $SLAVE_STATUS ]]; then echo Replication not configured exit 1 fi # 检查IO和SQL线程是否运行且无延迟 IO_RUNNING$(echo $SLAVE_STATUS | grep Slave_IO_Running: | awk {print $2}) SQL_RUNNING$(echo $SLAVE_STATUS | grep Slave_SQL_Running: | awk {print $2}) SECONDS_BEHIND$(echo $SLAVE_STATUS | grep Seconds_Behind_Master: | awk {print $2}) if [[ $IO_RUNNING ! Yes ]] || [[ $SQL_RUNNING ! Yes ]] || [[ $SECONDS_BEHIND ! 0 ]]; then echo Replication unhealthy: IO$IO_RUNNING, SQL$SQL_RUNNING, Delay$SECONDS_BEHIND exit 1 fi # 检查自身是否可写排除read_only锁定 READ_ONLY$($MYSQL_CMD SELECT read_only; 2/dev/null | tail -1) if [[ $READ_ONLY ON ]]; then echo MySQL is in read_only mode exit 1 fi exit 0这个脚本的精妙之处在于它不仅检查MySQL进程存活更验证了复制链路双向健康Slave_IO_Running和Slave_SQL_Running均为Yes和实时性Seconds_Behind_Master0。如果B库同步延迟了5秒脚本返回非0Keepalived会将B节点优先级减5使其无法抢夺VIP从而避免业务流量切到一个“半同步”节点上。3.4 脑裂Split-Brain防护用仲裁设备打破僵局双主最大的噩梦不是单点故障而是脑裂——A、B都认为对方死了各自启动VIP网络里出现两个192.168.1.100。此时应用连接随机落到任一节点写入冲突瞬间爆发。我们曾在一个物流调度系统里遭遇过脑裂持续47秒导致32个运单状态被不同节点覆盖最终靠人工比对kafka日志才恢复。破解脑裂只有两种可靠方式第三方仲裁Quorum或网络层隔离Fencing。我们选择后者因为更轻量、更可控。具体是在Keepalived配置中加入nopreempt和preempt_delay并配合一个简单的ping检测# 在vrrp_instance块内添加 nopreempt # BACKUP节点不主动抢占除非MASTER彻底失联 preempt_delay 300 # MASTER恢复后等待300秒再尝试抢回VIP避免震荡 # 新增vrrp_script检测网关连通性防网络分区 vrrp_script check_gateway { script /bin/ping -c 1 -W 1 192.168.1.1 /dev/null 21 interval 1 weight -2 fall 2 rise 1 }当A、B之间网络中断但各自都能连外网check_gateway会失败两节点优先级同时降低VIP自动释放。此时业务会短暂中断约3秒但绝不会出现双VIP。等网络恢复优先级高的A节点在300秒后平滑接管B节点安静同步。这个策略牺牲了“100%可用”但换取了“100%数据安全”是生产环境必须接受的trade-off。4. 实操全流程与关键环节验证从部署到压测的每一步4.1 部署顺序严格遵循七步法双主Keepalived部署不是“先装MySQL再配Keepalived”而是一个环环相扣的七步链。跳过任何一步都可能埋下数月后才爆发的隐患网络层准备确认A、B节点在同一子网ping互通arping -I eth0 192.168.1.100能收到响应VIP未被占用MySQL基础配置在A、B上分别配置server-id、log-bin、auto_increment参数重启MySQL创建复制账号在A、B上均执行CREATE USER repl% IDENTIFIED BY repl_pass; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES;GTID初始化按3.2节流程用xtrabackup完成A→B的初始同步反向配置复制在B库执行CHANGE MASTER TO MASTER_HOSTA_IP, ..., MASTER_AUTO_POSITION1; START SLAVE;在A库执行同理指向BKeepalived安装与配置在A、B上安装keepalived按3.3节配置keepalived.conf特别注意priority和state的对应关系启动与验证先启MySQL再启Keepalivedsystemctl start keepalived最后用ip addr show确认VIP只在A节点出现。我们曾在一个政务服务平台部署时跳过了第1步“网络层准备”结果发现B节点的eth0网卡被云厂商分配了169.254.x.x链路本地地址导致VRRP组播包无法送达A节点。Keepalived日志里全是VRRP_Instance(VI_1) Dropping received VRRP packet...排查了6小时才发现是云网络配置问题。所以“先看网络再动配置”是血泪教训。4.2 关键验证点五个必测场景清单配置完成后绝不能只看ip addr有VIP就认为成功。必须模拟真实故障验证五个核心场景场景操作步骤预期结果验证命令1. 主库MySQL进程崩溃kill -9 $(pgrep mysqld)on AVIP在3秒内漂移到Bmysql -h192.168.1.100 -e SELECT 1返回1ip addr show eth0 | grep 192.168.1.1002. 主库复制中断在A库执行STOP SLAVE;VIP漂移到B因check_mysql.sh检测到Slave_IO_RunningNotail -f /var/log/messages | grep Transition to MASTER3. 网络分区脑裂模拟iptables -A OUTPUT -d B_IP -j DROPon AA、B均释放VIPip addr无192.168.1.100ip addr show eth0 | grep 192.168.1.1004. VIP漂移后写入一致性切换后在B库插入INSERT INTO test(id,name) VALUES(1001,A);再查SELECT * FROM test WHERE id1001;A库在10秒内同步该记录GTID自动追平SELECT * FROM test WHERE id1001;on A5. 故障恢复后服务回归恢复A库MySQL和网络等待300秒VIP自动切回AB库继续同步无数据丢失mysql -h192.168.1.100 -e INSERT ...写入成功其中第4项“写入一致性”最容易被忽略。我们曾在一个教育平台上线前测试发现VIP切到B后A库的Seconds_Behind_Master一直显示NULL查SHOW SLAVE STATUS才发现Slave_SQL_Running_State是Waiting for dependent transaction to commit——原因是B库刚切为主GTID_EXECUTED集合里多了新事务A库需要先执行完依赖事务才能继续。此时需耐心等待或手动STOP SLAVE; START SLAVE;触发重试。这不是故障而是GTID复制的正常收敛过程。4.3 压测实录用sysbench模拟真实业务冲击配置验证通过后必须用压测工具检验极限能力。我们用sysbench 1.0.20对双主集群进行四轮压测每轮10分钟QPS递增# 准备数据100张表每表10万行 sysbench oltp_read_write \ --db-drivermysql \ --mysql-host192.168.1.100 \ # 直连VIP --mysql-port3306 \ --mysql-usersbtest \ --mysql-passwordsbtest \ --tables100 \ --table-size100000 \ prepare # 压测命令QPS从500逐步加到5000 sysbench oltp_read_write \ --db-drivermysql \ --mysql-host192.168.1.100 \ --mysql-port3306 \ --mysql-usersbtest \ --mysql-passwordsbtest \ --tables100 \ --table-size100000 \ --threads64 \ --time600 \ --report-interval10 \ run压测结果如下单位QPSQPS档位A节点CPUB节点CPUVIP漂移次数平均延迟(ms)失败率50022%18%08.20%150045%41%012.70%300078%75%024.30.02%500099%98%1A节点OOM187.512.3%关键发现在4000 QPS时A节点vm.swappiness60导致频繁swapfree -h显示SwapUsed达3.2G此时Keepalived的check_mysql.sh因IO等待超时误判A为故障VIP漂移到B。解决方案是将vm.swappiness调至1并增加innodb_buffer_pool_size到物理内存的70%。压测不是为了追求极限QPS而是为了找到那个“临界点”并在其下方留出30%余量——这才是生产环境的敬畏之心。5. 常见问题与独家排障技巧那些文档里不会写的坑5.1 “VIP漂移了但应用连不上”连接池与DNS缓存的双重陷阱现象Keepalived日志显示Transition to MASTERip addr确认VIP已在B节点但应用日志里全是Connection refused或Connection timeout。根因往往不在数据库而在应用侧连接池未及时刷新HikariCP默认connection-test-query是SELECT 1但若连接池里还存着A节点的旧连接这些连接会持续发送请求到已下线的A库。解决方案是配置connection-init-sqlSELECT 1leak-detection-threshold60000并启用remove-abandoned-on-borrowtrue。DNS缓存未失效Java应用默认DNS缓存networkaddress.cache.ttl30秒即使VIP已漂移JVM仍会把192.168.1.100解析到A的MAC地址。强制设置-Dnetworkaddress.cache.ttl0或改用IP直连jdbc:mysql://192.168.1.100:3306/db。我们曾在一个Spring Boot项目里遇到此问题排查了4小时才发现是application.yml里写了spring.datasource.urljdbc:mysql://db-vip:3306/db而db-vip这个域名在/etc/hosts里静态绑定到了A节点IP。删掉那行127.0.0.1 db-vip问题立解。5.2 “同步延迟突然飙升”从磁盘IO到网络MTU的全链路排查现象SHOW SLAVE STATUS里Seconds_Behind_Master从0跳到300且持续不降。排查必须按OSI模型从下往上物理层iostat -x 1看%util是否100%await是否50ms。曾有一个案例是B节点磁盘使用了老旧的SATA SSDr_await峰值达120ms导致SQL线程执行慢网络层mtr --report 192.168.1.100看是否有丢包ethtool eth0确认MTU是否一致A、B必须都是1500否则TCP分片导致binlog传输卡顿MySQL层SHOW PROCESSLIST看State是否卡在Reading event from the relay logIO线程慢或executingSQL线程慢。前者查网络后者查innodb_log_file_size是否过小频繁刷盘。终极技巧用pt-heartbeat工具监控真实延迟。在A库创建心跳表每秒写入时间戳B库通过SELECT UNIX_TIMESTAMP() - UNIX_TIMESTAMP(ts)计算延迟。这个值比Seconds_Behind_Master更准确因为它不依赖MySQL内部计时器而是端到端测量。5.3 “脑裂后数据不一致”用pt-table-checksum做终极校验即使有GTID脑裂后仍可能产生不一致。因为GTID只保证“事务被执行”不保证“执行结果相同”。比如A库执行UPDATE t SET cc1 WHERE id1B库执行UPDATE t SET cc*2 WHERE id1两个事务GTID不同但都成功写入最终c值取决于谁后执行。解决方案是定期用pt-table-checksum做全表校验# 在A库执行VIP指向A时 pt-table-checksum \ --host127.0.0.1 \ --userchecksum_user \ --passwordxxx \ --databasesdb1,db2 \ --no-check-binlog-format \ --replicatetest.checksums # 在B库执行校验对比 pt-table-sync \ --sync-to-master \ --host127.0.0.1 \ --usersync_user \ --passwordxxx \ --databasesdb1,db2 \ --replicatetest.checksums我们设定每天凌晨2点自动执行生成HTML报告邮件发送。上线一年来共发现3次微小不一致均为DDL操作未同步均在业务低峰期手动修复。记住双主不是免检产品而是需要定期“体检”的精密仪器。5.4 “Keepalived频繁切换”从日志里挖出真正的元凶现象/var/log/messages里每分钟都有VRRP_Instance(VI_1) Entering MASTER STATE和Leaving MASTER STATE。不要急着调advert_int先看日志关键词Kernel is denying ARP requests说明内核ARP过滤太严需echo 1 /proc/sys/net/ipv4/conf/all/arp_ignoreVRRP_Script(check_mysql) failed检查check_mysql.sh是否因timeout超时把mysql命令加上--connect-timeout2Netlink: Invalid argumentKeepalived版本过低2.0.19升级即可。最隐蔽的案例某团队的B节点/etc/hosts里把A节点IP映射到了localhost导致check_mysql.sh里的mysql -hA_IP实际连到了127.0.0.1而127.0.0.1上MySQL没开远程访问脚本永远返回1。改/etc/hosts世界清静。6. 运维规范与长期演进让双主架构越用越稳6.1 日常巡检清单10分钟完成一次健康扫描别等报警才行动。我们给DBA制定的每日巡检清单只需10分钟VIP状态curl -s http://192.168.1.100:3306 | head -1返回空即通复制延迟mysql -h127.0.0.1 -e SHOW SLAVE STATUS\G | grep -E (Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running)磁盘空间df -h /var/lib/mysql预警阈值85%binlog增长ls -lt /var/lib/mysql/mysql-bin.* | head -5确认最近24小时有新文件生成Keepalived日志grep -i error\|fail\|transition /var/log/messages | tail -10。这五项覆盖了90%的潜在风险。我们曾用这个清单在一次磁盘满前2小时发现/var/lib/mysql使用率达84.7%及时清理binlog避免了服务中断。6.2 版本升级策略MySQL与Keepalived的协同演进MySQL从5.7升级到8.0或Keepalived从1.x升级到2.x绝不能“一键升级”。必须遵循三阶段策略阶段一兼容性验证在测试环境部署新版本用mysql_upgrade检查系统表兼容性确认pt-table-checksum能正常工作。阶段二滚动升级先升级B节点停B的Keepalived → 升级MySQL → 启动MySQL → 启动Keepalived → 等待同步追平 → VIP切到B → 升级A节点。全程VIP不中断。阶段三功能回归升级后立即执行4.2节的五个验证场景特别是GTID一致性SELECT GLOBAL.GTID_EXECUTED比对。我们升级到MySQL 8.0.32时在阶段二发现caching_sha2_password插件与旧版JDBC驱动不兼容应用报Unknown initial character set index。解决方案是升级驱动到8.0.33并在MySQL里执行ALTER USER sbtest% IDENTIFIED WITH mysql_native_password BY sbtest;。升级不是技术动作而是风险控制流程。6.3 架构演进思考双主之后路在何方双主Keepalived是很好的起点但不是终点