概述

DDL(Data Definition Language)定义了数据库内部对象(库、表、列等)上的操作语义。

在MySQL中,根据是否阻塞DML,DDL可分为Copy DDL和Online DDL。其中Copy DDL在执行过程全程持有表级MDL X锁,禁止了其他并发DML操作;而从5.6版本开始引入了Online DDL,即只在元数据操作阶段持有表级MDL X锁,而其他数据操作阶段降级为MDL S锁以支持并发DML操作。

MySQL 8.0版本对DDL的进行了重大调整,除了引入原子DDL功能外,在8.0.12版本还为Online DDL的ALGORITHM参数引入了新的选项INSTANT,即对于只涉及到数据字典中的元数据的DDL操作可以在持有X锁的很短时间内“立即”完成元数据更新并返回。彼时支持在表的最后新增数据列、新增或删除虚拟列、修改列默认值、修改ENUM/SET的定义、修改索引类型、表重命名。前序数据库内核月报文章MySQL特性-Instant Add Column介绍了MySQL自8.0.12版本引入的Instant DDL Add Column的功能。本文介绍MySQL后续版本对Instant DDL的增强。

Instant Rename Column

MySQL 8.0.28版本增强Instant DDL对列重命名的支持。

在Instant Add Column基础上实现列重命名较为简单直接。

  • prepare_inplace_altr_table阶段加强对列重命名的检查,新增禁止外键引用列上执行instant rename column
  • 新增Alter Flag INNOBASE_INSTANT_ALLOWED用于简化判断
  • 新增INSTANT_OPERATION枚举类型来定义Instant DDL操作类型。
  1. const Alter_inplace_info::HA_ALTER_FLAGS INNOBASE_INSTANT_ALLOWED =
  2. Alter_inplace_info::ALTER_COLUMN_NAME |
  3. Alter_inplace_info::ADD_VIRTUAL_COLUMN |
  4. Alter_inplace_info::DROP_VIRTUAL_COLUMN |
  5. Alter_inplace_info::ALTER_VIRTUAL_COLUMN_ORDER |
  6. Alter_inplace_info::ADD_STORED_BASE_COLUMN;
  7. enum class INSTANT_OPERATION {
  8. COLUMN_RENAME_ONLY, // 仅列重命名
  9. VIRTUAL_ADD_DROP_ONLY, // 仅添加或删除虚拟列
  10. VIRTUAL_ADD_DROP_WITH_RENAME, // 添加或删除虚拟列 + 列重命名
  11. INSTANT_ADD, // 添加列、添加或删除虚拟列 + 列重命名
  12. NONE
  13. };

Instant Drop Column

元数据修改

  • 系统表中引入TOTAL_ROW_VERSIONS,用于追踪当前表执行INSTANT DDL的次数,初始值为0,每次执行INSTANT Add/Drop column都会递增该值。每次表数据被重建时都会将TOTAL_ROW_VERSIONS重置为0。TOTAL_ROW_VERSIONS最大值为64,即最多支持连续64次INSTALT Add/Drop column操作。
  • dd::Column的se_private_data中新增version_added、version_dropped、physical_pos,分别表示当且列添加、删除时的row_version和记录行中的物理位置。

check_if_supported_inplace_alter

该函数用于在Server层通过handler接口检查存储引擎对当前DDL操作关于inplace逻辑的支持情况,新版本新增以下条件检查

  • 检查dict_table_t上当前行版本号,如果行版本已经达到最大支持的版本(即64),如果DDL语句指定使用INSTANT算法则报错返回,否则回退为INPLACE DDL并重建表数据。
  • 调用Instant_ddl_impl::is_instant_add_possible函数检查新增列(如果存在)是否会导致行记录大小超过物理页允许的最大大小。

Instant_ddl_impl类

在新版本中,所有Instant DDL逻辑操作的实现都封装在Instant_ddl_impl类中。处理前述is_instant_add_possible函数外,主要是封装了commit_inplace_alter_table函数需要调用commit_instant_ddl函数

  1. // 对于其他只有非列操作、列重命名以及虚拟列相关的Instant操作保持原来版本
  2. case Instant_Type::INSTANT_ADD_DROP_COLUMN:
  3. dd_copy_private(*m_new_dd_tab, *m_old_dd_tab); // 从旧表中复制se_private_data到新的dd::Table中
  4. populate_to_be_instant_columns(); // 收集Instant DDL期间发生重命名、Drop和Add列的信息
  5. if (!m_cols_to_drop.empty()) { // 存在Instant drop的列
  6. commit_instant_drop_col();
  7. |--> commit_instant_drop_col_low
  8. |----> dd_copy_table_columns // 从旧的dd::Table复制列的元信息,跳过Instant Drop列,如果是第一次执行Instant Add/Drop还需要设置列Physical Position信息
  9. |----> dd_drop_instant_columns
  10. |--> copy_dropped_columns // 如果旧的dd::Table上已经存在Instant DDL删除过的列,需要将其从旧的dd::Table复制到新的dd::Table上,设置HIDDEN属性,并复制元信息
  11. // 遍历当前Instant DDL要删除的列,在dd::Table中添加新的dd::Column对象,设置HIDDEN属性,列名以_dropped_<row_version>为后缀
  12. |--> instant_update_table_cols_count // 更新dict_table_t上的列计数
  13. }
  14. if (!m_cols_to_add.empty()) { // 存在Instant add的列
  15. commit_instant_add_col();
  16. |--> commit_instant_add_col_low
  17. |----> dd_copy_table_columns
  18. |----> dd_add_instant_columns
  19. |--> copy_dropped_columns // 如果旧表上已经存在Instant DDL删除过的列,需要将其从旧表复制到新表上,设置HIDDEN属性,并复制元信息
  20. // 遍历当前Instant DDL要添加的列,在dd::Column的se_private_data中设置version_added、physical_pos、default/default_null属性的值
  21. |--> instant_update_table_cols_count // 更新dict_table_t上的列计数
  22. |----> dd_update_v_cols // 如果存在新增的虚拟列,设置相关属性信息
  23. }
  24. m_dict_table->current_row_version++; // 递增table上的row version
  25. innobase_discard_table(m_thd, m_dict_table); // 清楚统计信息,设置discard_after_ddl为true

实验

创建以下2张表,并插入100,000行数据

  1. CREATE TABLE sale(
  2. cn INT NOT null,
  3. vn INT NOT null,
  4. pn INT NOT null,
  5. dt DATE NOT null,
  6. qty INT NOT null,
  7. prc FLOAT NOT null
  8. ) ENGINE InnoDB;
  9. CREATE TABLE sale_2 LIKE sale;

在sale上执行INSTANT DDL,sale_2上执行INPLACE DDL,执行效果如下图所示。 pic 其中sale表上的INSTANT DDL执行耗时0.01s,更新TOTAL_ROW_VERSIONS为1;而sale_2表上的INPLACE DDL执行耗时0.22s,更新了TABLE_ID但未更新TOTAL_ROW_VERSIONS。

Reference