MySQL 5.7 DDL 非原子,因元数据(.frm 文件)与 InnoDB 数据分离,crash 易致 .frm 与表空间不一致;8.0 通过将元数据统一存入 InnoDB 系统表并纳入事务,实现真正原子 DDL。

MySQL 5.7 的 DDL 不是原子的,根本原因在于它没有事务型数据字典——元数据操作和存储引擎操作分属两套不兼容的机制,失败时无法回滚。
DDL 中断后为什么 5.7 会留下半截表或损坏的 .frm 文件
5.7 把表结构信息存在独立的 .frm 文件里,而真实数据在 InnoDB 表空间中。执行 ALTER TABLE 时,Server 层先改 .frm,再让 InnoDB 做数据迁移;如果中途 crash,可能出现:
-
.frm已更新但 InnoDB 数据没写完 → 查询报错 “Table doesn’t exist” 或 “Incorrect information in file” - InnoDB 数据已重建但
.frm还是旧版 →SHOW CREATE TABLE显示错误结构,甚至导致复制中断 - 临时表(
#sql-*)残留,手动清理风险高,DROP TABLE可能卡住
这些都不是“部分成功”,而是元数据与数据层彻底失同步,只能靠人工介入或从备份恢复。
8.0 的事务性数据字典如何让 DDL 真正原子化
8.0 把所有元数据(库、表、列、索引、约束等)统一存进 InnoDB 系统表,比如 mysql.tables、mysql.columns、mysql.indexes。这意味着:
- DDL 操作变成对这些系统表的 INSERT/UPDATE/DELETE + 对用户表的物理变更,全部包裹在同一个事务中
- 哪怕在最后一步写入数据页时 crash,InnoDB 的崩溃恢复(crash recovery)也能回滚整个事务,包括元数据变更
- 不再有
.frm、.TRG等外部文件,所有定义都受 ACID 保护
例如执行 ALTER TABLE t ADD COLUMN c INT,8.0 实际上是:
START TRANSACTION; INSERT INTO mysql.columns (...) VALUES (...); -- 新列定义 UPDATE mysql.tables SET se_private_data = ... WHERE name = 't'; -- 更新表描述 -- 同事务内完成数据页扩展与默认值填充 COMMIT;
任何环节失败,整个事务丢弃,对外不可见。
为什么你升级后仍可能遇到“非原子感”问题
原子 DDL 是默认行为,但以下情况会让它失效或表现异常:
- 使用了不支持原子 DDL 的存储引擎(如 MyISAM、CSV),
ALTER TABLE会退化为 5.7 式非原子流程 - 开启了
old_mode=NO_TABLE_OPTIONS或其他兼容模式,可能绕过新字典路径 - DDL 涉及外部组件(如触发器调用 UDF、写磁盘文件的插件),这部分逻辑不在事务范围内
- 显式加了
ALGORITHM=COPY且过程中被 kill,虽然元数据能回滚,但临时拷贝的中间表可能残留(需清理innodb_file_per_table=ON下的孤立.ibd)
检查是否真正走原子路径,可查 performance_schema.events_statements_history 中语句的 TRANSACTION_ID 是否非空,或观察 error log 是否出现 “Atomic DDL: commit” 类日志。
真正容易被忽略的是:原子 DDL 依赖整个数据字典表都在 InnoDB 上——如果你从 5.7 升级而来,且未运行 mysql_upgrade(或 8.0+ 的等效初始化),mysql 库下仍有 MyISAM 系统表(如 proc、help_topic),那部分 DDL 依然不具备原子性。升级后第一件事不是调优,是确认 SELECT table_name, engine FROM information_schema.tables WHERE table_schema='mysql' AND engine!='InnoDB'; 返回空结果。


















