MySQL 5.7 的 DDL 非原子根本原因是元数据分散存储且无事务保护:.frm、.TRG、.OPT 等文件与 MyISAM 系统表、InnoDB 字典各自独立,崩溃时无法统一回滚,导致部分成功、残留临时文件、主从不一致等问题。

MySQL 5.7 的 DDL 为什么不是原子的
根本原因在于元数据分散存储、无事务保护。5.7 中表结构信息(.frm 文件)、触发器(.TRG)、数据库选项(.OPT)都以独立文件形式存在,同时 Server 层用 MyISAM 引擎存部分系统表(如 mysql.tables),而 InnoDB 自己又维护一份字典数据。这些地方互不感知、无法统一回滚。
一旦执行 DROP TABLE t1, t2 过程中 mysqld 崩溃,可能出现 t1 被删掉但 t2 还在的情况;或者 ALTER TABLE 半途失败,留下残留的 #sql-*.ibd 和 #sql-*.frm 临时文件,后续操作直接报错。
- 崩溃恢复时,MyISAM 系统表不支持事务回滚,InnoDB 引擎层也看不到 Server 层的
.frm变更 - Binlog 记录的是 DDL 语句本身,不是“事务”,主从复制时可能只同步了部分效果
-
information_schema查询依赖多源拼接,结果可能不一致
MySQL 8.0 如何靠 Data Dictionary 实现原子 DDL
8.0 把所有元数据——库、表、列、索引、约束、权限对象——全收进 InnoDB 系统表里,比如 mysql.tables、mysql.columns、mysql.indexes,全部是事务型表。DDL 不再是“改文件 + 改表”,而是“在一个事务里批量更新这些系统表 + 写 mysql.innodb_ddl_log 日志”。崩溃时靠 InnoDB 自身的 crash recovery 回滚整个事务即可。
-
mysql.innodb_ddl_log是关键:它记录前滚(redo)和回滚(undo)操作指令,例如“重命名文件 X 为 Y”或“删除临时表 Z” - DDL 分成 prepare → execute → commit → post-ddl 四阶段,文件系统操作(如 rename、unlink)被推迟到 post-ddl 阶段,确保事务已提交才真正落盘
- 所有涉及字典变更的操作(包括
CREATE DATABASE、ALTER USER、CREATE INDEX)都走同一套路径,不再区分引擎
哪些 DDL 真正享受原子性,哪些只是“看起来像”
不是所有 DDL 都自动获得原子保障。只有明确由 InnoDB 管理的对象、且语句被完整纳入字典事务流程的,才算。典型支持范围包括:
-
CREATE/DROP/ALTER表、索引、视图、存储过程、函数、触发器、事件、用户、角色 -
TRUNCATE TABLE(本质是重建,走原子流程) -
RENAME TABLE t1 TO t2, t3 TO t4(整组生效或全不生效)
但以下情况不保证原子性:
- 涉及非 InnoDB 引擎的表,如
ENGINE=MyISAM或ENGINE=ARCHIVE—— 它们不参与字典事务 -
INSTALL PLUGIN、CREATE SERVER等插件/组件类语句,仍走旧路径 - 跨引擎混合操作,例如
ALTER TABLE t1 ENGINE=InnoDB, ADD COLUMN x INT:列添加走原子流程,但引擎转换本身不保证(尽管 8.0 大多数情况下能清理干净)
升级后要注意的隐性行为变化
你没改任何 SQL,但应用可能出问题——因为 5.7 的“半成功”现在变成“全失败”。比如以前写了个脚本循环 DROP TABLE IF EXISTS t1; CREATE TABLE t1...,假设中间某次 DROP 出错(如权限不足),5.7 会继续往下走;8.0 如果 DROP 失败,整个语句终止,后续 CREATE 根本不执行。
- 显式指定
ENGINE=InnoDB仍是好习惯,避免因默认配置差异引发意外 -
innodb_strict_mode=ON在 8.0 是默认,对字段长度、默认值等校验更严,可能让原来 5.7 能建的表在 8.0 报错 -
mysql.innodb_ddl_log表不能手动删,它是内部日志表,删了会导致后续 DDL 恢复失败
最易被忽略的一点:原子 DDL 不等于可嵌套事务。你依然不能在 BEGIN ... COMMIT 里执行 CREATE TABLE —— DDL 自动触发隐式提交,这是 MySQL 协议层限制,和原子性无关。











