主从切换后事务报错的四大原因:一是新主库read_only/super_read_only未关闭;二是旧主库长事务持有MDL锁未释放;三是客户端仍连接旧主库(DNS/VIP未刷新);四是GTID不一致导致回滚失败。

主从切换后事务报错 ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
主从切换后,原从库升为主库但未清除 read_only=ON,这是最常见原因。MySQL 5.7+ 默认在从库启用 read_only=ON,切换后若仅改 server_id 或重启服务,该参数仍保留。
检查方式:SELECT @@read_only; —— 返回 1 即处于只读状态。
- 临时修复:
SET GLOBAL read_only = OFF;(需有SUPER或SYSTEM_VARIABLES_ADMIN权限) - 永久修复:修改配置文件
my.cnf,删掉或注释掉read_only=1行,再重启或动态关闭(推荐先动态关,确认业务正常后再调整配置) - 注意:
super_read_only=ON会覆盖read_only,必须一并检查:SELECT @@super_read_only;;若为1,需先执行SET GLOBAL super_read_only = OFF;才能改read_only
事务执行卡住或超时,SHOW PROCESSLIST 显示 Waiting for table metadata lock
主从切换瞬间,旧主库可能仍有长事务、未提交的 DML 或 DDL 在运行,其持有的元数据锁(MDL)未释放,而新主库在重放 relay log 或直接接受新连接时,试图操作同一张表,就会被阻塞。
这不是权限或配置问题,而是锁资源未清理干净的真实冲突。
- 查阻塞源头:
SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA NOT IN ('performance_schema', 'information_schema');(需开启performance_schema且metadata_locks消费器已启用) - 快速定位长事务:
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED LIMIT 5;,重点关注TRX_STATE='RUNNING'且TRX_STARTED时间远早于切换时刻的记录 - 安全终止(慎用):
KILL [trx_mysql_thread_id];,优先 kill 非核心应用线程,避免误杀复制线程(如io_thread、sql_thread)
应用连的是 VIP 或 DNS,但写请求仍打到旧主库(脑裂残留)
主从切换常依赖 VIP 漂移或 DNS 切换,但客户端连接池未及时刷新,或 DNS 缓存未过期,导致部分写请求继续发往原主库 —— 此时原主库通常已降为从库,read_only=ON,自然报错。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
这不是数据库自身问题,而是基础设施层与应用层协同失效。
- 验证方法:在报错客户端机器上执行
mysql -h $VIP -u $user -p -e "SELECT @@hostname, @@read_only;",对比返回的主机名和read_only值 - 连接池需支持“连接失效自动重试 + DNS 刷新”,例如 HikariCP 的
connection-test-query和refresh-interval(配合useSSL=false&allowPublicKeyRetrieval=true等兼容参数) - 更稳妥的做法是切换后强制清空客户端本地 DNS 缓存(
systemd-resolve --flush-caches或sudo dscacheutil -flushcache),并设置较短的 TTL(如 30s)
GTID 模式下事务回滚失败,提示 Could not execute Write_rows event on table ... Error_code: 1666
主从切换若未严格按 GTID 一致性步骤操作(比如跳过某些事务、reset master 后未同步 gtid_purged),新主库的 gtid_executed 集合可能缺失旧主库已提交事务的 GTID,导致从库重连后尝试重放时找不到对应事务上下文,进而引发写入失败或回滚异常。
这类错误往往在切换后数分钟才暴露,且不直接报 read_only,容易误判。
- 检查一致性:
SELECT @@gtid_executed;在新主库和所有从库上执行,比对是否包含相同前缀(如都含uuid:1-100) - 若新主库
gtid_executed缺失关键区间,不可强行SET GTID_NEXT跳过,应使用mysqldump --set-gtid-purged=OFF重建从库,或通过RESET MASTER; SET GLOBAL gtid_purged='...';补齐(仅当确认缺失事务已无业务影响) - 预防关键点:切换前确保所有从库
Seconds_Behind_Master = 0且Retrieved_Gtid_Set = Executed_Gtid_Set
实际处理时,read_only 和 super_read_only 状态最容易被忽略,而 MDL 锁冲突又最难被监控覆盖 —— 很多 DBA 只查了锁表却没查正在运行的事务,结果反复 kill 错对象。切主不是改个配置就完事,得盯住三件事:状态是否可写、锁是否干净、连接是否真到了新节点。










