数据库内核月报

数据库内核月报 - 2022 / 04

MySQL · InnoDB · Instant DDL扩展

Author: 无哈

概述

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基础上实现列重命名较为简单直接。

const Alter_inplace_info::HA_ALTER_FLAGS INNOBASE_INSTANT_ALLOWED =
    Alter_inplace_info::ALTER_COLUMN_NAME |
    Alter_inplace_info::ADD_VIRTUAL_COLUMN |
    Alter_inplace_info::DROP_VIRTUAL_COLUMN |
    Alter_inplace_info::ALTER_VIRTUAL_COLUMN_ORDER |
    Alter_inplace_info::ADD_STORED_BASE_COLUMN;

enum class INSTANT_OPERATION {
    COLUMN_RENAME_ONLY,           // 仅列重命名
    VIRTUAL_ADD_DROP_ONLY,        // 仅添加或删除虚拟列
    VIRTUAL_ADD_DROP_WITH_RENAME, // 添加或删除虚拟列 + 列重命名
    INSTANT_ADD,  // 添加列、添加或删除虚拟列 + 列重命名
    NONE
  };

Instant Drop Column

元数据修改

check_if_supported_inplace_alter

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

Instant_ddl_impl类

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

  // 对于其他只有非列操作、列重命名以及虚拟列相关的Instant操作保持原来版本
  case Instant_Type::INSTANT_ADD_DROP_COLUMN:
    dd_copy_private(*m_new_dd_tab, *m_old_dd_tab);  // 从旧表中复制se_private_data到新的dd::Table中
    populate_to_be_instant_columns(); // 收集Instant DDL期间发生重命名、Drop和Add列的信息
    if (!m_cols_to_drop.empty()) {  // 存在Instant drop的列
        commit_instant_drop_col();
        |--> commit_instant_drop_col_low
        |----> dd_copy_table_columns    // 从旧的dd::Table复制列的元信息,跳过Instant Drop列,如果是第一次执行Instant Add/Drop还需要设置列Physical Position信息
        |----> dd_drop_instant_columns
               |--> copy_dropped_columns  // 如果旧的dd::Table上已经存在Instant DDL删除过的列,需要将其从旧的dd::Table复制到新的dd::Table上,设置HIDDEN属性,并复制元信息
               // 遍历当前Instant DDL要删除的列,在dd::Table中添加新的dd::Column对象,设置HIDDEN属性,列名以_dropped_<row_version>为后缀
               |--> instant_update_table_cols_count // 更新dict_table_t上的列计数
    }
    if (!m_cols_to_add.empty()) {  // 存在Instant add的列
        commit_instant_add_col();
        |--> commit_instant_add_col_low
        |----> dd_copy_table_columns
        |----> dd_add_instant_columns
               |--> copy_dropped_columns  // 如果旧表上已经存在Instant DDL删除过的列,需要将其从旧表复制到新表上,设置HIDDEN属性,并复制元信息
               // 遍历当前Instant DDL要添加的列,在dd::Column的se_private_data中设置version_added、physical_pos、default/default_null属性的值
               |--> instant_update_table_cols_count // 更新dict_table_t上的列计数
        |----> dd_update_v_cols  // 如果存在新增的虚拟列,设置相关属性信息
    }

    m_dict_table->current_row_version++;  // 递增table上的row version
    innobase_discard_table(m_thd, m_dict_table);  // 清楚统计信息,设置discard_after_ddl为true

实验

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

CREATE TABLE sale(
    cn INT NOT null,
    vn INT NOT null,
    pn INT NOT null,
    dt DATE NOT null,
    qty INT NOT null,
    prc FLOAT NOT null
) ENGINE InnoDB;
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