### 13.1.1 原子数据定义语句支持

[](<>)[](<>)

MySQL 8.0 支持原子数据定义语言 (DDL) 语句。此功能被称为*原子 DDL*.原子 DDL 语句将与 DDL 操作关联的数据字典更新、存储引擎操作和二进制日志写入组合到单个原子操作中。操作要么被提交，适用的更改被持久化到数据字典、存储引擎和二进制日志中，要么被回滚，即使服务器在操作期间停止。

笔记

*原子 DDL*不是*事务性 DDL*.DDL 语句，无论是原子的还是其他的，都会隐式结束当前会话中活动的任何事务，就好像您已经完成了[`犯罪`](commit.html)在执行语句之前。这意味着 DDL 语句不能在另一个事务中执行，例如在事务控制语句中[`开始交易...提交`](commit.html)，或与同一事务中的其他语句组合。

通过在 MySQL 8.0 中引入 MySQL 数据字典，原子 DDL 成为可能。在早期的 MySQL 版本中，元数据存储在元数据文件、非事务表和特定于存储引擎的字典中，这需要中间提交。MySQL 数据字典提供的集中式事务元数据存储消除了这一障碍，从而可以将 DDL 语句操作重组为原子操作。

本节中的以下主题描述了原子 DDL 功能：

-   [支持的 DDL 语句](atomic-ddl.html#atomic-ddl-supported-statements)

-   [原子 DDL 特性](atomic-ddl.html#atomic-ddl-characteristics)

-   [DDL 语句行为的变化](atomic-ddl.html#atomic-ddl-statement-behavior)

-   [存储引擎支持](atomic-ddl.html#atomic-ddl-storage-engine-support)

-   [查看 DDL 日志](atomic-ddl.html#atomic-ddl-view-logs)

#### 支持的 DDL 语句

原子 DDL 功能支持表和非表 DDL 语句。与表相关的 DDL 操作需要存储引擎支持，而非表 DDL 操作则不需要。目前，只有`InnoDB`存储引擎支持原子 DDL。

-   支持的表 DDL 语句包括`创建`,`改变`， 和`降低`数据库、表空间、表和索引的语句，以及[`截断表`](truncate-table.html)陈述。

-   支持的非表 DDL 语句包括：

    -   `创建`和`降低`声明，以及，如果适用，`改变`存储程序、触发器、视图和可加载函数的语句。

    -   账户管理报表：`创建`, `改变`, `降低`，并且，如果适用，`改名`用户和角色的声明，以及[`授予`](grant.html)和[`撤销`](revoke.html)陈述。

    atomic DDL 功能不支持以下语句：

-   涉及存储引擎的表相关 DDL 语句`InnoDB`.

-   [`安装插件`](install-plugin.html)和[`卸载插件`](uninstall-plugin.html)陈述。

-   [`安装组件`](install-component.html)和[`卸载组件`](uninstall-component.html)陈述。

-   [`创建服务器`](create-server.html),[`更改服务器`](alter-server.html)， 和[`删除服务器`](drop-server.html)陈述。

#### 原子 DDL 特性

原子 DDL 语句的特点包括：

-   元数据更新、二进制日志写入和存储引擎操作（如果适用）被组合成一个原子操作。

-   在 DDL 操作期间，SQL 层没有中间提交。

-   适用时：

    -   数据字典、例程、事件和可加载函数缓存的状态与 DDL 操作的状态一致，这意味着更新缓存以反映 DDL 操作是成功完成还是回滚。

    -   DDL 操作中涉及的存储引擎方法不执行中间提交，并且存储引擎将自身注册为 DDL 操作的一部分。

    -   存储引擎支持 DDL 操作的重做和回滚，在*DDL 后*DDL 操作的阶段。

-   DDL 操作的可见行为是原子的，它改变了一些 DDL 语句的行为。看[DDL 语句行为的变化](atomic-ddl.html#atomic-ddl-statement-behavior).

#### DDL 语句行为的变化

本节介绍由于引入原子 DDL 支持而导致的 DDL 语句行为的变化。

-   [`删除表`](drop-table.html)如果所有命名表都使用原子 DDL 支持的存储引擎，则操作是完全原子的。该语句要么成功删除所有表，要么回滚。

    [`删除表`](drop-table.html)如果命名表不存在并且不进行任何更改，则无论存储引擎如何，都会失败并出现错误。下面的示例演示了这种行为变化，其中[`删除表`](drop-table.html)语句失败，因为命名表不存在：

    ```
    mysql> CREATE TABLE t1 (c1 INT);
    mysql> DROP TABLE t1, t2;
    ERROR 1051 (42S02): Unknown table 'test.t2'
    mysql> SHOW TABLES;
    +----------------+
    | Tables_in_test |
    +----------------+
    | t1             |
    +----------------+
    ```

    在引入原子 DDL 之前，[`删除表`](drop-table.html)为不存在的命名表报告错误，但为确实存在的命名表成功：

    ```
    mysql> CREATE TABLE t1 (c1 INT);
    mysql> DROP TABLE t1, t2;
    ERROR 1051 (42S02): Unknown table 'test.t2'
    mysql> SHOW TABLES;
    Empty set (0.00 sec)
    ```

    笔记

    由于这种行为变化，部分完成[`删除表`](drop-table.html)在 MySQL 8.0 副本上复制时，MySQL 5.7 复制源服务器上的语句失败。为避免这种失败情况，请使用`如果存在`语法在[`删除表`](drop-table.html)语句以防止不存在的表发生错误。

-   [`删除数据库`](drop-database.html)如果所有表都使用原子 DDL 支持的存储引擎，则它是原子的。该语句要么成功删除所有对象，要么回滚。但是，从文件系统中删除数据库目录是最后发生的，并且不是原子操作的一部分。如果由于文件系统错误或服务器停止而导致数据库目录的删除失败，则[`删除数据库`](drop-database.html)事务不回滚。

-   对于不使用原子 DDL 支持的存储引擎的表，表删除发生在原子之外[`删除表`](drop-table.html)要么[`删除数据库`](drop-database.html)交易。这样的表删除是单独写入二进制日志的，这样在中断的情况下将存储引擎、数据字典和二进制日志的差异限制在一个表中。[`删除表`](drop-table.html)要么[`删除数据库`](drop-database.html)手术。对于删除多个表的操作，不使用原子 DDL 支持的存储引擎的表将在使用的表之前删除。

-   [`创建表`](create-table.html),[`更改表`](alter-table.html),[`重命名表`](rename-table.html),[`截断表`](truncate-table.html),[`创建表空间`](create-tablespace.html)， 和[`删除表空间`](drop-tablespace.html)如果服务器在操作期间停止，则使用原子 DDL 支持的存储引擎的表的操作要么完全提交，要么回滚。在早期的 MySQL 版本中，这些操作的中断可能会导致存储引擎、数据字典和二进制日志之间的差异，或者留下孤立文件。[`重命名表`](rename-table.html)只有当所有命名表都使用原子 DDL 支持的存储引擎时，操作才是原子的。

-   从 MySQL 8.0.21 开始，在支持 atomic DDL 的存储引擎上，[`创建表...选择`](create-table-select.html)当使用基于行的复制时，语句将作为一个事务记录在二进制日志中。以前，它被记录为两个事务，一个用于创建表，另一个用于插入数据。两个事务之间或插入数据时的服务器故障可能会导致空表的复制。随着原子 DDL 支持的引入，[`创建表...选择`](create-table-select.html)语句现在对于基于行的复制是安全的，并且允许与基于 GTID 的复制一起使用。

    在同时支持原子 DDL 和外键约束的存储引擎上，不允许创建外键[`创建表...选择`](create-table-select.html)使用基于行的复制时的语句。以后可以使用外键约束添加[`更改表`](alter-table.html).

    什么时候[`创建表...选择`](create-table-select.html)作为原子操作应用时，在插入数据时会在表上保持元数据锁，从而防止在操作期间对表的并发访问。

-   [`下拉视图`](drop-view.html)如果命名视图不存在且不进行任何更改，则会失败。这个例子展示了行为的变化，其中[`下拉视图`](drop-view.html)语句失败，因为命名视图不存在：

    ```
    mysql> CREATE VIEW test.viewA AS SELECT * FROM t;
    mysql> DROP VIEW test.viewA, test.viewB;
    ERROR 1051 (42S02): Unknown table 'test.viewB'
    mysql> SHOW FULL TABLES IN test WHERE TABLE_TYPE LIKE 'VIEW';
    +----------------+------------+
    | Tables_in_test | Table_type |
    +----------------+------------+
    | viewA          | VIEW       |
    +----------------+------------+
    ```

    在引入原子 DDL 之前，[`下拉视图`](drop-view.html)为不存在的命名视图返回错误，但为确实存在的命名视图返回错误：

    ```
    mysql> CREATE VIEW test.viewA AS SELECT * FROM t;
    mysql> DROP VIEW test.viewA, test.viewB;
    ERROR 1051 (42S02): Unknown table 'test.viewB'
    mysql> SHOW FULL TABLES IN test WHERE TABLE_TYPE LIKE 'VIEW';
    Empty set (0.00 sec)
    ```

    笔记

    由于这种行为变化，部分完成[`下拉视图`](drop-view.html)在 MySQL 8.0 副本上复制时，在 MySQL 5.7 复制源服务器上的操作失败。为避免这种失败情况，请使用`如果存在`语法在[`下拉视图`](drop-view.html)用于防止不存在的视图发生错误的语句。

-   不再允许部分执行账户管理报表。帐户管理语句要么对所有指定用户成功，要么在发生错误时回滚并且无效。在早期的 MySQL 版本中，命名多个用户的帐户管理语句可能对某些用户成功而对其他用户失败。

    本示例演示了行为的变化，其中第二个[`创建用户`](create-user.html)语句返回错误但失败，因为它不能为所有命名用户成功。

    ```
    mysql> CREATE USER userA;
    mysql> CREATE USER userA, userB;
    ERROR 1396 (HY000): Operation CREATE USER failed for 'userA'@'%'
    mysql> SELECT User FROM mysql.user WHERE User LIKE 'user%';
    +-------+
    | User  |
    +-------+
    | userA |
    +-------+
    ```

    在引入原子 DDL 之前，第二个`创建用户`语句为不存在的命名用户返回错误，但为确实存在的命名用户返回成功：

    ```
    mysql> CREATE USER userA;
    mysql> CREATE USER userA, userB;
    ERROR 1396 (HY000): Operation CREATE USER failed for 'userA'@'%'
    mysql> SELECT User FROM mysql.user WHERE User LIKE 'user%';
    +-------+
    | User  |
    +-------+
    | userA |
    | userB |
    +-------+
    ```

    笔记

    由于这种行为变化，当在 MySQL 8.0 副本上复制时，MySQL 5.7 复制源服务器上部分完成的帐户管理语句会失败。为避免这种失败情况，请使用`如果存在`要么`如果不存在`帐户管理语句中的语法，以防止与命名用户相关的错误。

#### 存储引擎支持

目前，只有`InnoDB`存储引擎支持原子 DDL。不支持原子 DDL 的存储引擎不受 DDL 原子性的影响。涉及豁免存储引擎的 DDL 操作仍然能够引入在操作中断或仅部分完成时可能发生的不一致。

为了支持 DDL 操作的重做和回滚，`InnoDB`将 DDL 日志写入`mysql.innodb_ddl_log`表，它是一个隐藏的数据字典表，位于`mysql.ibd`数据字典表空间。

查看写入到`mysql.innodb_ddl_log`DDL 操作期间的表，启用[`innodb_print_ddl_logs`](innodb-parameters.html#sysvar_innodb_print_ddl_logs)配置选项。有关详细信息，请参阅[查看 DDL 日志](atomic-ddl.html#atomic-ddl-view-logs).

笔记

更改的重做日志`mysql.innodb_ddl_log`表会立即刷新到磁盘，而不管[`innodb_flush_log_at_trx_commit`](innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit)环境。立即刷新重做日志可避免数据文件被 DDL 操作修改但重做日志用于更改的情况`mysql.innodb_ddl_log`这些操作产生的表不会持久化到磁盘。这种情况可能会导致回滚或恢复期间出现错误。

这`InnoDB`存储引擎分阶段执行 DDL 操作。DDL 操作如[`更改表`](alter-table.html)可以执行*准备*和*履行*阶段之前多次*犯罪*阶段。

1.  *准备*：创建所需的对象并将 DDL 日志写入`mysql.innodb_ddl_log`桌子。DDL 日志定义如何前滚和回滚 DDL 操作。

2.  *履行*：执行 DDL 操作。例如，执行一个创建例程`创建表`手术。

3.  *犯罪*：更新数据字典并提交数据字典事务。

4.  *DDL 后*: 重播和删除 DDL 日志`mysql.innodb_ddl_log`桌子。为确保可以安全地执行回滚而不引入不一致，在最后阶段执行文件操作，例如重命名或删除数据文件。此阶段还从`mysql.innodb_dynamic_metadata`数据字典表[`删除表`](drop-table.html),[`截断表`](truncate-table.html)，以及其他重建表的 DDL 操作。

    DDL 日志被重放并从`mysql.innodb_ddl_log`期间表*DDL 后*阶段，无论 DDL 操作是提交还是回滚。DDL 日志应该只保留在`mysql.innodb_ddl_log`如果服务器在 DDL 操作期间停止，则表。在这种情况下，DDL 日志会在恢复后重放并删除。

    在恢复情况下，DDL 操作可能会在服务器重新启动时提交或回滚。如果在执行期间执行的数据字典事务*犯罪*如果重做日志和二进制日志中存在 DDL 操作的阶段，则认为该操作成功并前滚。否则，不完整的数据字典事务在`InnoDB`重放数据字典重做日志，回滚DDL操作。

#### 查看 DDL 日志

查看写入到`mysql.innodb_ddl_log`原子 DDL 操作期间的数据字典表，涉及`InnoDB`存储引擎，启用[`innodb_print_ddl_logs`](innodb-parameters.html#sysvar_innodb_print_ddl_logs)让 MySQL 将 DDL 日志写入`标准错误`.根据主机操作系统和 MySQL 配置，`标准错误`可能是错误日志、终端或控制台窗口。看[第 5.4.2.2 节，“默认错误日志目标配置”](error-log-destination-configuration.html).

`InnoDB`将 DDL 日志写入`mysql.innodb_ddl_log`表以支持 DDL 操作的重做和回滚。这`mysql.innodb_ddl_log`table 是一个隐藏的数据字典表，位于`mysql.ibd`数据字典表空间。与其他隐藏数据字典表一样，`mysql.innodb_ddl_log`在 MySQL 的非调试版本中不能直接访问表。（看[第 14.1 节，“数据字典模式”](data-dictionary-schema.html).) 的结构`mysql.innodb_ddl_log`表对应于这个定义：

```
CREATE TABLE mysql.innodb_ddl_log (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  thread_id BIGINT UNSIGNED NOT NULL,
  type INT UNSIGNED NOT NULL,
  space_id INT UNSIGNED,
  page_no INT UNSIGNED,
  index_id BIGINT UNSIGNED,
  table_id BIGINT UNSIGNED,
  old_file_path VARCHAR(512) COLLATE UTF8_BIN,
  new_file_path VARCHAR(512) COLLATE UTF8_BIN,
  KEY(thread_id)
);
```

-   `id`：DDL 日志记录的唯一标识符。

-   `线程 ID`: 每条 DDL 日志记录都被分配一个`线程 ID`，用于重放和删除属于特定 DDL 操作的 DDL 日志。涉及多个数据文件操作的 DDL 操作会生成多个 DDL 日志记录。

-   `类型`：DDL 操作类型。类型包括`自由`（删除索引树），`删除`（删除文件），`改名`（重命名文件），或`降低`（从`mysql.innodb_dynamic_metadata`数据字典表）。

-   `space_id`: 表空间 ID。

-   `page_no`：包含分配信息的页面；例如，索引树根页面。

-   `index_id`: 索引 ID。

-   `table_id`：表ID。

-   `old_file_path`: 旧的表空间文件路径。由创建或删除表空间文件的 DDL 操作使用；也由重命名表空间的 DDL 操作使用。

-   `新文件路径`: 新的表空间文件路径。由重命名表空间文件的 DDL 操作使用。

    此示例演示启用[`innodb_print_ddl_logs`](innodb-parameters.html#sysvar_innodb_print_ddl_logs)查看写入的 DDL 日志`斯特德尔`为一个`创建表`手术。


```
mysql> SET GLOBAL innodb_print_ddl_logs=1;
mysql> CREATE TABLE t1 (c1 INT) ENGINE = InnoDB;
```

```
[Note] [000000] InnoDB: DDL log insert : [DDL record: DELETE SPACE, id=18, thread_id=7,
space_id=5, old_file_path=./test/t1.ibd]
[Note] [000000] InnoDB: DDL log delete : by id 18
[Note] [000000] InnoDB: DDL log insert : [DDL record: REMOVE CACHE, id=19, thread_id=7,
table_id=1058, new_file_path=test/t1]
[Note] [000000] InnoDB: DDL log delete : by id 19
[Note] [000000] InnoDB: DDL log insert : [DDL record: FREE, id=20, thread_id=7,
space_id=5, index_id=132, page_no=4]
[Note] [000000] InnoDB: DDL log delete : by id 20
[Note] [000000] InnoDB: DDL log post ddl : begin for thread id : 7
[Note] [000000] InnoDB: DDL log post ddl : end for thread id : 7
```
