在 MySQL 5.6 中,官方开始支持更多的 ALTER TABLE 类型操作来避免数据拷贝,同时支持了在线上 DDL 的过程中不阻塞 DML 操作,真正意义上的实现了 Online DDL。然而并不是所有的 DDL 操作都支持在线操作,后面会附上 MySQL 官方文档对于 DDL 操作的总结。到了 MySQL 5.7,在 5.6 的基础上又增加了一些新的特性,比如:增加了重命名索引支持,支持了数值类型长度的增大和减小,支持了 VARCHAR 类型的在线增大等。但是基本的实现逻辑和限制条件相比 5.6 并没有大的变化。MySQL 8.0 对 DDL 的实现重新进行了设计,其中一个最大的改进是 DDL 操作支持了原子特性。另外,Online DDL 的 ALGORITHM 参数增加了一个新的选项:INSTANT,只需修改数据字典中的元数据,无需拷贝数据也无需重建表,同样也无需加排他 MDL 锁,原表数据也不受影响。整个 DDL 过程几乎是瞬间完成的,也不会阻塞 DML。

关于 MySQL 8.0 原子 DDL 的介绍可以参考:http://mysql.taobao.org/monthly/2020/05/05/

本文数据全部来自 MySQL 官方文档,此处进行一个集中的整理和总结: https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html

结合上面的表格,对 MySQL 当前 DDL 的执行模式总结如下:

INSTANT DDL 是 MySQL 8.0 引入的新功能,当前支持的范围较小,包括:

  • 修改二级索引类型
  • 新增列
  • 修改列默认值
  • 修改列 ENUM 值
  • 重命名表
  • 删除主键
  • 修改列数据类型
  • 修改表字符集

我们常说的 Online DDL,其实是从 DML 操作的角度描述的,如果 DDL 操作不阻塞 DML 操作,那么这个 DDL 就是 Online 的。当前非 Online 的 DDL 其实已经比较少了,主要有:

  • 新增全文索引
  • 删除主键
  • 修改列数据类型
  • 指定表字符集
  • 修改表字符集

更多详细的示例请参考上面的官方文档的地址。

最后讨论几个非常容易混淆的问题:

  1. Online DDL 不会锁表,可以随意的执行。
  2. 支持 INPLACE 算法的 DDL 一定是 Online 的。
  3. 对于支持 INPLACE 算法的 DDL,DDL 操作是原地修改数据,不需要额外的数据空间。

Online DDL 会不会锁表?要回答这个问题,首先要明确“锁表”的含义。很多 MySQL 用户经常在表无法正常的进行 DML 时就觉得是锁表了,这种说法其实过于宽泛,实际上能够影响 DML 操作的锁至少包括以下几种(默认为 InnoDB 表):

  • MDL 锁
  • 表锁
  • 行锁
  • GAP 锁

其中除了 MDL 锁是在 Server 层加的之外,其它三种都是在 InnoDB 层加的。具体的加锁逻辑不在此进行展开,但是需要明确一点:所有的操作(不管是 DDL 还是 DML 还是查询语句)都需要先拿 Server 层的 MDL 锁,然后再去拿 InnoDB 层的某个需要的锁。一个 DDL 的基本过程是这样的:

  1. 首选,在开始进行 DDL 时,需要拿到对应表的 MDL X 锁,然后进行一系列的准备工作;
  2. 然后将 MDL X 锁降级为 MDL S 锁,进行真正的 DDL 操作;
  3. 最后再次将 MDL S 锁升级为 MDL X 锁,完成 DDL 操作,释放 MDL 锁;

session 1 模拟了一个慢查询,然后 session 2 开始进行 DDL 操作,无法拿到 MDL X 锁,处于等到中。此时 session 3 需要执行一个查询,发现无法执行。实际上,在 session 1 结束前,表 t1 的所有操作都无法进行了,也可以说表 t1 “锁表”了。MySQL 5.7/8.0 可以在开启 performance_schema 的情况下直接查询 metadata_locks 表。阿里云 RDS 5.6 版本新增了 I_S.MDL_INFO 表,提供 MDL 的查询。

  1. +-------------+---------------+-------------+-------------+-----------------------+----------------------+---------------+-------------+-------------------+-----------------+----------------+
  2. | TABLE | mytest | t1 | NULL | 140730442220576 | SHARED_READ | TRANSACTION | GRANTED | sql_parse.cc:5916 | 1083 | 24 |
  3. | TABLE | mytest | t1 | NULL | 140730576178368 | SHARED_NO_READ_WRITE | TRANSACTION | PENDING | sql_parse.cc:5916 | 1091 | 3 |
  4. +-------------+---------------+-------------+-------------+-----------------------+----------------------+---------------+-------------+-------------------+-----------------+----------------+
  5. 3 rows in set (0.00 sec)

明确了上面的概念之后,再回到我们的问题,Online DDL 是不是不锁表?如果非要回答,那么只能说,Online DDL 并不是绝对安全,更不是可以随意的执行。线上操作还是需要在业务低峰期谨慎操作。

从概念上来说,INPLACE 和 Online 是两个不同维度的事情。COPY 和 INPLACE 指的是 DDL 内部的执行逻辑,可以简单的理解成:COPY 是在 Server 层的操作,INPLACE 是在 InnoDB 层的操作。而用户更加关心 Online 与否,通常只与一个问题有关:是否允许并发 DML。两个基本结论:

  1. COPY 算法执行的 DDL 肯定不是 Online 的;
  2. INPLACE 算法执行的 DDL 不一定是 Online 的;

前面我们提到过,MySQL 内部对于 DDL 的 ALGORITHM 有两种选择:INPLACE 和 COPY(8.0 新增了 INSTANT,但是使用范围较小)。COPY 算法理解起来相对简单一点:创建一张临时表,然后将原表的数据拷贝到临时表中,最后再用临时表替换原表。对于上面的步骤,由于需要将原表的数据拷贝到临时表中,所以肯定需要消耗额外的数据空间。

那么对于支持 INPLACE 算法的 DDL,是不是不需要额外的数据空间?答案是:需要。其实之所以会问这个问题,还是因为对 INPLACE 本身的理解出现了偏差。简单来说:INPLACE 描述的是表,而不是数据文件。只要不创建临时表,那么都是 INPLACE 的。

  • 增加主键
  • 重建主键
  • 新增列(8.0 支持 INSTANT DDL,不需要)
  • 删除列
  • 调整列顺序
  • 删除列默认值
  • 增加列默认值
  • 修改表的 ROW_FORMAT

本文主要是对 MySQL Online DDL 进行了一个简单的整理和总结,更多关于 MySQL 内部实现细节和源码的分析,请关注后续文章。