MySQL 8.0 新特性之原子 DDL

原文地址:MySQL 8.0 Reference Manual

MySQL 8.0 开始支持原子性的数据定义语言(DDL),也称为原子 DDL。一个原子 DDL 语句将相关的数据字典更新、存储引擎操作以及写入二进制日志组合成单一的原子事务。当事务正在处理时出现服务器故障,该事务可能被提交,相应的变更会保存到数据字典更新、存储引擎更改以及二进制日志中;也可能被整体回滚。

MySQL 8.0 引入的集成数据字典使得原子 DDL 成为可能。在之前的 MySQL 版本中,元数据分散在文件、非事务型的表以及存储引擎相关的字典中,导致 DDL 语句在执行过程中间存在提交,无法作为一个事务整体进行操作。MySQL 数据字典提供的集中式事务型元数据存储解决了这个问题,能够将 DDL 语句重构成具有原子性的事务操作。

支持的 DDL 语句

原子 DDL 支持与表相关的 DDL 语句和与表无关的 DDL 语句。与表相关的 DDL 操作需要存储引擎的支持,而与表无关的 DDL 操作不需要存储引擎的支持。目前,只有 InnoDB 存储引擎支持原子 DDL。

  • 与表相关的原子 DDL 包括数据库、表空间、表、索引的 CREATE、ALTER 以及 DROP 语句,以及 TRUNCATE TABLE 语句。
  • 与表无关的原子 DDL 包括:
    • 存储过程、触发器、视图以及用户定义函数(UDF)的 CREATE 和 DROP 语句,以及适用的 ALTER 语句。
    • 帐户管理语句:用户和角色的 CREATE、ALTER、DROP 语句,以及适用的 RENAME 语句,以及 GRANT
      和 REVOKE 语句。

以下语句不支持原子 DDL 操作:

  • 非 InnoDB 存储引擎上的表相关 DDL 语句。
  • INSTALL PLUGIN 和 UNINSTALL PLUGIN 语句。
  • INSTALL COMPONENT 和 UNINSTALL COMPONENT 语句。
  • CREATE SERVER、ALTER SERVER 以及 DROP SERVER 语句。

原子 DDL 的特性

原子 DDL 语句具有以下特性:

  • 将可能存在的元数据更新、二进制日志写入以及存储引擎操作组合成单个事务。

  • DDL 操作过程中不存在 SQL 层的中间提交操作。

  • 如果存在的话:

    • 数据字典、过程、事件以及用户定义函数的缓存状态与 DDL 操作的状态一致,意味着 DDL 操作成功或者回滚时,缓存都会进行相应更新。
    • DDL 操作涉及的存储引擎相关修改不会执行中间的提交操作,而是作为 DDL 事务的一部分进行处理。
    • 存储引擎支持 DDL 操作的重做和回滚,这些操作发生在 DDL 操作的 Post-DDL 阶段。
  • 用户可见的 DDL 操作结果具有原子性,这种方式改变了某些 DDL 操作的行为。参见下文。

    注意
    任何 DDL 语句,包括原子性或其他的 DDL,都会隐式地结束当前事务,就像在执行语句之前运行了一个 COMMIT 操作一样。这就意味着 DDL 语句不能位于其他事务之中,不能位于事务控制语句(例如 START TRANSACTION … COMMIT)之中,也不能与同一个事务中的其他语句组合使用。

DDL 语句行为变更

原子 DDL 的引入给 DDL 语句的行为带来了一些变化:

  • 如果参数中所有的表都使用了支持原子 DDL 的存储引擎,DROP TABLE 语句整体上都是一个原子操作。要么成功删除所有的表,要么回滚整个操作。

    如果某个表不存在,DROP TABLE 将会返回一个错误,此时无论使用哪种存储引擎,都不会产生任何操作。以下是一个示例,由于表 t2 不存在, DROP TABLE 语句将会失败:

    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 返回一个错误,但是仍然会删除存在的表:

    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)
    

    注意
    由于这种行为的改变,在一个 MySQL 5.7 主节点上部分成功的 DROP TABLE 语句复制到 MySQL 8.0 从节点上时将会失败。为了避免这个问题,可以使用 DROP TABLE 语句的 IF EXISTS 选项避免表不存在时的错误。

  • 如果某个数据库中的所有表都使用支持原子 DDL 的存储引擎进行存储,删除该数据库的 DROP DATABASE 语句具有原子性。要么成功删除所有的对象,要么回滚整个操作。不过,从文件系统删除数据库目录的操作最后执行,不属于原子事务的一部分。如果由于文件系统错误或者服务器故障,导致删除数据库目录的操作失败,不会回滚 DROP DATABASE 事务。

  • 对于存储引擎不支持原子 DDL 操作的表,删除操作在原子 DROP TABLE 或者 DROP DATABASE 事务之外执行。这种表的删除操作将单独写入二进制日志,这样在发生 DROP TABLE 或 DROP DATABASE 操作中断时,最多导致一个表的存储引擎、数据字典以及二进制日志之间存在不一致。对于删除多个表的操作,先删除不支持原子 DDL 操作的表,然后再执行原子 DDL 删除其他表。

  • 对于存储引擎支持原子 DDL 操作的表,执行 CREATE TABLE、ALTER TABLE、RENAME TABLE、TRUNCATE TABLE、CREATE TABLESPACE 以及 DROP TABLESPACE 操作要么全部成功,要么在出现服务器故障时整体回滚。在之前的 MySQL 版本中,这种操作的中断将会导致存储引擎、数据字典以及二进制日志之间的不一致性,或者遗留下孤立文件。RENAME TABLE 只有当所有的表都支持原子 DDL 操作时才具有原子性。

  • 如果要删除的视图不存在,DROP VIEW 将会失败,不会执行任何操作。例如:

    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 将会返回一个错误,但是仍然会删除存在的视图:

    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)
    

    注意
    由于这种行为的改变,在一个 MySQL 5.7 主节点上部分成功的 DROP VIEW 语句复制到 MySQL 8.0 从节点上时将会失败。为了避免这个问题,可以使用 DROP VIEW 语句的 IF EXISTS 选项避免视图不存在时的错误。

  • 账户管理语句不再允许部分执行成功。同一个语句中的多个账户管理要么同时成功,要么全部失败。在之前的 MySQL 版本中,同时管理多个用户的账户管理语句可能对于部分用户操作成功,而另一部分用户操作失败。

    以下示例演示了这种行为的改变,第二个 CREATE USER 语句返回一个错误,不会执行任何操作:

    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 之前,第二个 CREATE USER 语句虽然返回了一个错误,但是仍然创建了一个新的用户:

    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 5.7 主节点上部分成功的账户管理语句复制到 MySQL 8.0 从节点上时将会失败。为了避免这个问题,可以使用账户管理语句的 IF EXISTS 或 IF NOT EXISTS 选项避免表相关的错误。

存储引擎支持

目前,只有 InnoDB 存储引擎支持原子 DDL。不支持原子 DDL 的存储引擎不具有 DDL 操作的原子性。对于不支持原子 DDL 操作的存储引擎,DDL 操作在中断或者部分执行时,仍然可能出现不数据的一致性。

为了支持 DDL 操作的重做和回滚,InnoDB 在执行过程中将 DDL 日志写入 mysql.innodb_ddl_log 表中,它是一个隐藏的数据字典表,存储在 mysql.ibd 数据字典表空间中。

要想查看 DDL 操作过程中写入 mysql.innodb_ddl_log 表中的日志,可以启用 innodb_print_ddl_logs 配置选项。具体参考下文。

注意
无论参数 innodb_flush_log_at_trx_commit 设置为何值,表 mysql.innodb_ddl_log 中数据变化的重做日志都会立即同步到磁盘中。立即刷新重做日志是为了避免出现 DDL 操作已经完成数据文件的修改,但是修改 mysql.innodb_ddl_log 表的重做日志没有持久化到磁盘中。这种情况下将会导致回滚或恢复操作的失败。

InnoDB 存储引擎执行 DDL 操作时分阶段进行处理。某些 DDL 操作,例如 ALTER TABLE,可能会在执行提交阶段之前多次执行准备阶段和执行阶段的操作。

  1. 准备:创建所需的对象,并且将 DDL 日志写入 mysql.innodb_ddl_log 表中。DDL 日志定义了如何前滚和回滚相应的 DDL 操作。
  2. 执行:执行 DDL 操作。例如,为 CREATE TABLE 执行创建操作。
  3. 提交:更数据字典并提交数据字典事务。
  4. Post-DDL:重放并删除 mysql.innodb_ddl_log 表中的 DDL 日志。为了能够安全地执行回滚操作而不会导致不一致性,对于文件的操作,例如重命名数据文件或移动数据文件,放在这个最后的阶段执行。这个阶段还会为 DROP TABLE、TRUNCATE TABLE 以及其他重建表的 DDL 操作删除数据字典表 mysql.innodb_dynamic_metadata 中的动态元数据。

无论事务被提交还是回滚,在 Post-DDL 阶段都会重放并删除 mysql.innodb_ddl_log 表中的 DDL 日志。只有当服务器在执行 DDL 操作的过程中出现故障时,才会在 mysql.innodb_ddl_log 表中保留 DDL 日志。这种情况下,在服务器恢复之后执行 DDL 日志的重放和删除。

对于需要进行恢复的情况,服务器重启之后,可能执行 DDL 事务的提交,也可能执行事务的回滚。如果在提交阶段执行的数据字典事务已经记录在重做日志和二进制日志中,就会认为 DDL 操作已经成功,并且执行前滚操作。否则,当 InnoDB 重放数据字典重做日志的时候,将会回滚不完整的数据字典事务,并且回滚 DDL 事务。

查看 DDL 日志

要查看 InnoDB 存储引擎相关的原子 DDL 操作写入 mysql.innodb_ddl_log 表中的日志,可以启用 innodb_print_ddl_logs 参数,将 MySQL DDL 日志输出到 stderr。取决于操作系统和 MySQL 配置,stderr 可以是错误日志、终端或者控制台窗口。参考 Section 5.4.2.2, “默认错误日志路径”

InnoDB 将 DDL 日志写入 mysql.innodb_ddl_log 表,用于支持 DDL 操作的重做和回滚。mysql.innodb_ddl_log 表是一个隐藏的数据字典表,存储在数据字典表空间 mysql.ibd 中。与其他的隐藏数据字典表一样,mysql.innodb_ddl_log 表不能在非调试版本的 MySQL 中直接访问。(See 第 14.1 节, “数据字典模式”。) 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 日志记录的唯一标识符。
  • thread_id:每个 DDL 日志记录都与一个 thread_id 相关联,用于重放和删除某个特定 DDL 事务的 DDL 日志。涉及多个数据文件操作的 DDL 事务将会生成多条 DDL 日志记录。
  • type:DDL 操作的类型。包括 FREE (删除一棵索引树)、DELETE(删除一个文件)、RENAME (重命名文件)或者 DROP(从数据字典表 mysql.innodb_dynamic_metadata 中删除元数据)。
  • space_id:表空间 ID.
  • page_no:包含分配信息的页面;例如,索引树的根页面。
  • index_id:索引 ID。
  • table_id:表 ID。
  • old_file_path:旧的表空间文件路径。用于创建或删除表空间文件的 DDL 操作;以及重命名表空间的 DDL 操作。
  • new_file_path:新的表空间文件路径。用于重命名表空间文件的 DDL 操作。

以下示例启用了 innodb_print_ddl_logs 参数,显示了一个 CREATE TABLE 语句输出的 DDL 日志。

mysql> SET GLOBAL log_error_verbosity=3;
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

人生本来短暂,你又何必匆匆!点个赞再走吧!

猜你喜欢

转载自blog.csdn.net/horses/article/details/85257000