• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 原子数据定义语句支持

    MySQL 8.0支持原子数据定义语言(DDL)语句。此功能称为原子DDL。原子DDL语句将数据字典更新,存储引擎操作和与DDL操作相关联的二进制日志写入操作组合到单个原子事务中。即使事务在操作期间停止,事务也可以提交,并在数据字典,存储引擎和二进制日志中保留适用的更改,或者回滚。

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

    本部分的以下主题下介绍了原子DDL功能:

    • 支持的DDL语句
    • 原子DDL特性
    • DDL语句行为的变化
    • 存储引擎支持
    • 参见DDL日志

    支持的DDL语句

    原子DDL功能支持表DDL语句和非表DDL语句。与表相关的DDL操作需要存储引擎支持,而非表DDL操作则不需要。当前,仅InnoDB存储引擎支持原子DDL。

    • 支持的表DDL语句包括CREATEALTERDROP数据库,表空间,表和索引的TRUNCATE TABLE语句以及该语句。
    • 受支持的非表DDL语句包括:

      • CREATEDROP语句,以及(如果适用)ALTER存储程序,触发器,视图和用户定义函数(UDF)的语句。
      • 账户管理语句:CREATEALTERDROP,,如果适用,RENAME报表用户和角色,以及GRANTREVOKE报表。

    原子DDL功能不支持以下语句:

    • 与表相关的DDL语句,其中包含以外的存储引擎InnoDB
    • INSTALL PLUGINUNINSTALL PLUGIN声明。
    • INSTALL COMPONENTUNINSTALL COMPONENT声明。
    • CREATE SERVERALTER SERVERDROP SERVER语句。

    原子DDL特性

    原子DDL语句的特征包括以下内容:

    • 元数据更新,二进制日志写入和存储引擎操作(如果适用)组合为一个事务。
    • 在DDL操作期间,SQL层没有中间提交。
    • 适用时:

      • 数据字典,例程,事件和UDF缓存的状态与DDL操作的状态一致,这意味着更新缓存以反映DDL操作是成功完成还是回滚。
      • DDL操作中涉及的存储引擎方法不执行中间提交,并且存储引擎将自身注册为DDL事务的一部分。
      • 存储引擎支持DDL操作的重做和回滚,该操作在DDL操作的后DDL阶段执行。
    • DDL操作的可见行为是原子的,这会更改某些DDL语句的行为。请参见 DDL语句行为的更改。
    注意

    DDL语句(原子的或其他方式)隐式结束当前会话中处于活动状态的任何事务,就好像您COMMIT在执行该语句之前已执行了。这意味着DDL语句不能在另一个事务内,在诸如的事务控制语句内执行,也不能START TRANSACTION ... COMMIT与同一事务内的其他语句组合。

    DDL语句行为的变化

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

    • DROP TABLE如果所有命名表都使用原子DDL支持的存储引擎,则操作完全是原子的。该语句要么成功删除所有表,要么回滚。

      DROP TABLE如果命名表不存在,并且无论存储引擎如何,都不会进行更改,则失败并显示错误。下面的示例演示了这种行为上的变化,其中该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)
      
      注意

      由于这种行为上的变化,DROP TABLE当在MySQL 8.0从属服务器上复制时,在MySQL 5.7主服务器上的部分完成的语句将失败。为避免出现这种失败情况,请IF EXISTSDROP TABLE语句中使用语法以防止不存在的表发生错误。

    • DROP DATABASE如果所有表都使用原子DDL支持的存储引擎,则为原子。该语句要么成功删除所有对象,要么回滚。但是,最后一次从文件系统中删除数据库目录不是原子事务的一部分。如果由于文件系统错误或服务器停止而无法删除数据库目录,DROP DATABASE则不会回滚事务。
    • 对于不使用原子DDL支持的存储引擎的表,表删除发生在原子DROP TABLEDROP DATABASE事务之外。此类表删除操作将分别写入二进制日志,这在中断DROP TABLEDROP DATABASE操作的情况下,最多将存储引擎,数据字典和二进制日志之间的差异限制为一个表。对于删除多个表的操作,未使用不支持原子DDL的存储引擎的表将被删除。
    • CREATE TABLEALTER TABLERENAME TABLETRUNCATE TABLECREATE TABLESPACE,和DROP TABLESPACE对使用原子DDL支持的存储引擎的表操作将被完全提交或在其运行期间推出,如果服务器暂停回来。在早期的MySQL版本中,这些操作的中断可能会导致存储引擎,数据字典和二进制日志之间出现差异,或者留下孤立的文件。RENAME TABLE仅当所有命名表都使用原子DDL支持的存储引擎时,操作才是原子的。
    • DROP VIEW如果命名视图不存在,并且不进行任何更改,则失败。在此示例中演示了行为上的变化,其中该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)
      
      注意

      由于这种行为上的变化,DROP VIEW当在MySQL 8.0从属服务器上进行复制时,在MySQL 5.7主服务器上执行的部分完成操作将失败。为避免这种失败情况,请IF EXISTSDROP VIEW语句中使用语法以防止不存在的视图发生错误。

    • 不再部分执行帐户管理对帐单。帐户管理语句对所有命名用户都成功,也可以回滚,如果发生错误,则无效。在早期的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 8.0从属服务器上进行复制时,MySQL 5.7主服务器上的部分完成的帐户管理语句将失败。为了避免出现这种失败情况,请在帐户管理语句中使用IF EXISTS或使用IF NOT EXISTS语法,以防止与命名用户相关的错误。

    存储引擎支持

    当前,仅InnoDB存储引擎支持原子DDL。不支持原子DDL的存储引擎免于DDL原子性。涉及豁免的存储引擎的DDL操作仍然能够引入不一致,这种不一致可能在操作中断或仅部分完成时发生。

    为了支持DDL操作的重做和回滚,InnoDB请将DDL日志写入mysql.innodb_ddl_log表,该表是驻留在mysql.ibd数据字典表空间中的隐藏数据字典表。

    要参见mysql.innodb_ddl_log在DDL操作期间写入表的DDL日志,请启用innodb_print_ddl_logs配置选项。有关更多信息,请参见参见DDL日志。

    注意

    mysql.innodb_ddl_log不管innodb_flush_log_at_trx_commit设置如何,用于更改表的重做日志都会立即刷新到磁盘。立即刷新重做日志可以避免DDL操作修改数据文件,但mysql.innodb_ddl_log这些操作导致的表更改重做日志不会持久保存到磁盘的情况。这种情况可能会在回滚或恢复期间导致错误。

    InnoDB存储引擎分阶段执行DDL操作。DDL操作(例如)ALTER TABLE可能在提交阶段之前多次执行“准备”和“执行”阶段。

    1. 准备:创建所需的对象并将DDL日志写入mysql.innodb_ddl_log表中。DDL日志定义了如何前滚和后滚DDL操作。
    2. 执行:执行DDL操作。例如,执行操作的创建例程CREATE TABLE
    3. 提交:更新数据字典并提交数据字典事务。
    4. DDL后:重播并从mysql.innodb_ddl_log表中删除DDL日志。为了确保可以安全地执行回滚而不会引起不一致,在此最后阶段执行文件操作,例如重命名或删除数据文件。这一阶段还从删除的动态元数据mysql.innodb_dynamic_metadata的数据字典表DROP TABLETRUNCATE TABLE和该重建表其他DDL操作。

    mysql.innodb_ddl_logDDL后阶段,无论事务是提交还是回滚,都会重播DDL日志并将其从表中删除。仅mysql.innodb_ddl_log当在DDL操作期间停止服务器时,DDL日志才应保留在表中。在这种情况下,将恢复DDL日志并在恢复后将其删除。

    在恢复情况下,重新启动服务器时,可能会提交或回退DDL事务。如果在重做日志和二进制日志中存在在DDL操作的Commit阶段执行的数据字典事务,则该操作被认为是成功的并且被前滚。否则,当InnoDB重播数据字典重做日志时,将回滚不完整的数据字典事务,并回滚DDL事务。

    参见DDL日志

    要参见mysql.innodb_ddl_log在涉及InnoDB存储引擎的原子DDL操作过程中写入数据字典表innodb_print_ddl_logs的DDL日志,请启用 MySQL将DDL日志写入stderr。根据主机操作系统和MySQL配置,stderr可能是错误日志,终端或控制台窗口。请参见“MySQL服务器错误日志”。

    InnoDB将DDL日志写入mysql.innodb_ddl_log表以支持DDL操作的重做和回滚。该mysql.innodb_ddl_log表是一个隐藏的数据字典表,位于mysql.ibd数据字典表空间中。与其他隐藏数据字典表一样,该mysql.innodb_ddl_log表不能在MySQL的非调试版本中直接访问。(请参见“数据字典架构”。)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以参见写入到DDL日志strderr用于CREATE TABLE操作。

    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