前言
MySQL 5.7 的字典信息保存在非事务表中,并且存放在不同的文件中(.FRM,.PAR,.OPT,.TRN,.TRG 等)。所有 DDL
操作都不是 Crash Safe,而且对于组合 DDL(ALTER
多个表)会出现有的成功有的失败的情况,而不是总体失败。这样主从复制就出现了问题,也导致基于复制的高可用系统不再安全。 一键获取最新面试真题合集文档
什么是原子 DDL?
DDL 是指数据定义语言(Data Definition Language),负责数据结构的定义与数据对象的定义。原子 DDL 是指一个 DDL 操作是不可分割的,要么全成功要么全失败。
有哪些限制?
MySQL 8.0 只有 InnoDB 存储引擎支持原子 DDL。
**支持语句:**数据库、表空间、表、索引的 CREATE、ALTER 以及 DROP 语句,以及 TRUNCATE TABLE 语句。
MySQL 8.0 系统表均以 InnoDB 存储引擎存储,涉及到字典对象的均支持原子 DDL。
**支持的语句:**存储过程、触发器、视图以及用户定义函数(UDF)的 CREATE 和 DROP 、ALTER 操作,用户和角色的 CREATE、ALTER、DROP 语句,以及适用的 RENAME 语句,以及 GRANT 和 REVOKE 语句。
不支持的语句:
- INSTALL PLUGIN、UNINSTALL PLUGIN
- INSTALL COMPONENT、UNINSTALL COMPONENT
- REATE SERVER、ALTER SERVER、DROP SERVER
实现原理是什么?
首先,8.0 将字典信息存放到事务引擎的系统表(InnoDB 存储引擎)中。这样 DDL 操作转变成一组对系统表的 DML 操作,从而失败后可以依据事务引擎自身的事务回滚保证系统表的原子性。
似乎 DDL 原子性就此就可以完成,但实际上并没有这么简单。首先字典信息不光是系统表,还有一组字典缓存,如:
- Table Share 缓存
- DD 缓存
- InnoDB 中的 dict
此外,字典信息只是数据库对象的元数据,DDL 操作不光要修改字典信息,还要实实在在的操作对象,以及对象本身在内存中缓存。
- 表空间
- Dynamic meta
- Btree
- ibd 文件
- buffer pool 中表空间的 page 页
此外,binlog 也要考虑 DDL 失败的情况。
因此,原子 DDL 在处理 DDL 失败的时候,不光是直接回滚系统表的数据,而且也要保证内存缓存,数据库对象也能回滚到一致状态。
实现细节
为了解决 DDL 失败情况中数据库对象的回滚,8.0 引入了系统表 DDL_LOG。该表在 mysql 库中。不可见,也不能人为操作。如果想了解该表的结果,先编译一个debug版的MySQL:
`SET SESSION debug='+d,skip_dd_table_access_check';
show create table mysql.innodb_ddl_log;`
可以看到如下表结构:
`CREATE TABLE `innodb_ddl_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`thread_id` bigint unsigned NOT NULL,
`type` int unsigned NOT NULL,
`space_id` int unsigned DEFAULT NULL,
`page_no` int unsigned DEFAULT NULL,
`index_id` bigint unsigned DEFAULT NULL,
`table_id` bigint unsigned DEFAULT NULL,
`old_file_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`new_file_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `thread_id` (`thread_id`)
) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC`
在 8.0 中,这个表需要满足两个场景以及两个任务:
场景 1: 符合 DDL 失败的场景,需要回滚部分完成的 DDL。
场景 2:DDL 进行中,发生故障(掉电、软硬件故障等),重启机器需要完成部分 DDL。
两个任务:
任务 1:失败后回滚,执行反向操作。
任务 2:如果成功,则执行清理工作。
也许有人会问,为什么执行成功需要执行清理工作呢?
之所以要执行清理工作,因为 ibd 文件和索引一旦删除就不能恢复。为了实现回滚,DDL 删除这些对象时候,并不是真正删除,而是先将它们备份一下,以备回滚时使用。所以只有确认 DDL 已经执行成功,这些备份对象不需要了,才执行清理工作。
举个例子
为了将这个原理将清楚,我们流程相对简单的 CREATE T