场景描述
某业务实例在例行变更后执行重启,重启后数据库实例可正常启动,但出现以下异常现象:
访问
'table' 时从客户端或应用侧收到报错:table 'parent' doesn't exist;。主库上看似正常,但从库因无法加载该表导致复制线程异常断开(Replica SQL thread 报错)。
定时备份任务失败,备份工具在执行 SHOW TABLES / FLUSH TABLES / 导出该表时报表不存在。
错误日志中可观察到 InnoDB 在 open table 阶段加载外键关系失败的相关信息。
原因分析
正常行为(对照)
当一个字段被外键引用时,MySQL 会保护该字段不被随意修改。例如直接执行:
ALTER TABLE parent MODIFY id VARCHAR(10) CHARACTER SET utf8mb4;
会返回预期的保护性报错:
ERROR 1833 (HY000): Cannot change column 'id': used in a foreign key constraint
Bug 触发条件
当会话设置 foreign_key_checks=0 时,MySQL 本意是“跳过 DML 阶段的逐行外键校验”,但该开关同时也跳过了 ALTER 阶段对“字段被外键引用”的保护性检查,导致上述 ALTER 成功执行。
然而该 ALTER 仅更新了 .frm 文件中的列定义(如 utf8 → utf8mb4),并未同步更新 InnoDB 数据字典中外键索引的 collation 记录,也未重建子表上的外键辅助索引。此时父表字段字符集已变为 utf8mb4_general_ci,而子表外键索引记录仍停留在 utf8_general_ci。
重启后校验失败(故障爆发点)
实例重启后,InnoDB 在 open table 阶段会执行 dict_load_foreigns 外键索引一致性校验,发现:
父表引用字段:utf8mb4_general_ci。
子表外键索引记录:utf8_general_ci。
两者不匹配,InnoDB 拒绝加载该表,对外表现为 Table 'parent' doesn't exist。由于该表是复制链路与备份集的一部分,进而引发从库异常断开、备份失败等连锁故障。
根因小结
foreign_key_checks=0→ ALTER 绕过"被外键引用字段"保护→ 仅改 .frm,未同步 InnoDB 字典 collation / 重建子表外键索引→ 实例重启后 dict_load_foreigns 校验不一致→ 表拒绝加载 → Table doesn't exist → 从库断开 / 备份失败
解决思路
1. 禁止在变更流程中关闭外键检查。
2. 升级至 MySQL 8.0:8.0 使用事务数据字典,对被外键引用字段的 ALTER 检查不受 foreign_key_checks 影响,始终强制拦截,可彻底规避该问题。
处理步骤
1. 在执行变更操作时请确保目标字段未被任何外键引用,否则不要执行 SET foreign_key_checks=0 后再对该类字段做 ALTER。
2. 将数据库版本升级至 MySQL 8.0,升级操作可参见 升级数据库版本。
影响范围
版本 | 是否受影响 | 说明 |
MySQL 5.6(全系列) | 受影响 | foreign_key_checks=0 可绕过 ALTER 外键保护 |
MySQL 5.7(全系列) | 受影响 | 同上 |
MySQL 8.0、MySQL 8.4 | 不受影响 | 事务数据字典:被引用字段的 ALTER 检查始终强制拦截 |