跳转至

3.在线DDL

DDL 变更

DDL(Data Definition Language)是数据库内部的对象进行创建、删除、修改的操作语言,主要包括:加减列、更改列类型、加减索引等类型。

数据库的模式(schema)会随着业务的发展不断变化,如果没有高效的 DDL 功能,每一次变更都有可能影响业务,甚至产生故障。

在数据库的发展历史中,DDL (Data Definition Language) 操作一直是一个重要的挑战。在主流商业数据库的早期版本中,传统的 DDL 操作,往往都需要对数据表进行锁定,以防止在操作过程中发生数据不一致。

这种锁定操作会导致数据库在 DDL 操作期间无法提供服务,对于需要 7*24 运行的大型现代应用来说,这是难以接受的。

为了解决这个问题,ORACLE 自 9i 引入 Online Table Redefinition,MySQL 自 5.6 引入 OnlineDDL,都旨在显著减少(或消除)更改数据库对象所需的应用程序停机时间(该特性以下统称 OnlineDDL)。

MySQL 在 8.0 以前就已经支持 Online DDL,在执行时能够不阻塞其它 DML(Insert/Update/Delete)操作,但许多重要的 DDL 操作,如加减列等,仍旧需要等待很长时间(根据数据量的大小)才会生效。

DDL 变更是应用版本部署过程中的常见操作,在核心业务系统中 DDL 操作不能保证在线完成,是会影响业务 7x24 小时的。

最常见的操作莫过于在线加一个字段或者索引,如果数据量比较大的话,伴随而来的往往是长时间的等待,操作期间对表的访问都会不可用,所以一般只能等到凌晨操作,简直就是梦魇一般的存在。

发展历程

MySQL 5.5 版本中就加入了 INPLACE DDL 方式,但是因为实现的问题,依然会阻塞 INSERTUPDATEDELETE 操作,这也是 MySQL 早期版本长期被吐槽的原因之一。

MySQL 5.6 中,官方开始支持更多的 ALTER TABLE 类型操作来避免数据拷贝,同时支持了在线上 DDL 的过程中不阻塞 DML 操作,真正意义上的实现了 Online DDL

MySQL 8.0 进一步引入了 Instant 算法,这是 DDL 操作的一个重大突破。Instant DDL 允许立即执行某些 DDL 操作,如尾部添加字段、修改字段名、修改表名等等,均无需锁定表或复制数据。

MySQL DDL 的注意事项

MySQL 在大表上的 DDL 会带来耗时较久、负载较高、额外空间占用、MDL、主从同步延时等情况。需要特别引起重视。

  • DDL 所需时间 DDL 的执行时间,和很多因素相关,如果需要比较精确的时间预估,建议在测试环境提前做测试。 可以新建一个测试实例,将备份数据导出到测试实例,执行 DDL 操作,判断执行时间,作为对线上执行的一个估计。但是请注意,该估计仍然可能不准确,因为线上实例的负载可能会比测试实例高。

  • 负载 所有方式对大表做 DDL 都会增加负载,只是程度的不同,主要为 IO 的负载。如果是 IO 使用非常高的实例,建议在 IO 较小的时间段执行 DDL 操作。 如果 Online DDL 执行失败,则回滚有可能会是一项昂贵的操作;

  • 额外空间占用 copy、inplace rebuild-table、gh-ost、pt-online-schema-change,都会将表完整复制一份出来再做 DDL 变更,因此会使用和原表空间一样大(甚至更大,如果是加列的操作的话)的额外空间,另外还会生成大量的临时日志。要特别注意剩余空间,确保空间充裕,不然可能导致 DDL 过程中磁盘写满。

  • 临时日志文件大小(innodb_online_alter_log_max_size),当 DDL 执行过程当中允许并发执行 DML 操作时的日志大小需求。 临时排序文件大小(tmpdir),当 DDL 执行过程中表需要 rebuild 时临时排序文件是放在 tmpdir 指定的路径下的,需要保证该路径下的磁盘空间充足。 临时排序文件都足够容纳所有二级索引以及聚簇索引的主键列,最终合并到新表或索引后,临时排序文件会被删除。 在 MySQL5.7.11 及之后版本当中新增系统参数(innodb_tmpdir)专门用来指定 Online DDL 产生排序文件的路径。
    临时中间表文件大小当有些 DDL 执行过程中表需要 rebuild 时会在当前表所在路径下产生临时中间表文件,临时中间表文件大小可能需要与原表大小一致,在 DDL 执行过程当中产生。

MDL 锁

所有方式做 DDL 均会产生 MDL(metadata lock)。除了 copy 模式会有持续性的锁(DDL 的整个过程期间无法向该表写入任何数据)之外,其他方式的 MDL 均为短暂的锁。

DDL 语法

ALTER TABLE tbl_name
    [alter_specification [, alter_specification] ...]
    [partition_options]

alter_specification:
    table_options
  | ADD [COLUMN] col_name column_definition
        [FIRST | AFTER col_name]
  | ADD [COLUMN] (col_name column_definition,...)
  ....
  | ALGORITHM [=] {DEFAULT|INSTANT|INPLACE|COPY}

 --备注:
 -- 1.DEFAULT:MySQL自己选择锁定资源最少的方式
 -- 2.INSTANT:只需要更新数据字典中的元数据, 很快完成
 -- 3.INPLACE:此变更由InnoDB引擎独立完成, 不需要使用Redo log等, 可以节省开销
 -- 4.COPY:此变更会重建聚簇索引, 执行DDL的时候会创建临时表

MySQL DDL 各种算法

copy 算法

这是早期较简单较低级的实现方法,MySQL 会在内部建立一个新的临时表,把源表的所有数据写入到临时表,在此期间无法对源表进行数据写入。

MySQL 在完成临时表的写入之后,用临时表替换掉源表。这个算法主要被早期(MySQL<=5.5)版本所使用。

MySQL 内部具体实现步骤如下:

  1. 按照表 A 的定义新建一个临时表 B
  2. 对表 A 加读锁,不允许执行 DML(期间表 A 只读)
  3. 在临时表 B 上执行 DDL 指定的操作(比如增加和删除列等)
  4. 逐行查出将表 A 中的数据查出来并插入到表 B
  5. 删除表 A
  6. 将表 B 重命名为 A

在 2-4 的过程中,如果表 A 数据量比较大,拷贝到表 B 的过程会消耗大量时间,并占用额外的存储空间。此外,由于 DDL 操作占用了表 A 的读锁,所以表 A 上的 DDL 和 DML 都将阻塞无法提供服务。

ALTER TABLE table1 ADD COLUMN column1 int ALGORITHM=COPY;

inplace 算法

当表发生 DDL 操作,可能会出现该表格数分钟甚至数小时不可访问,性能及响应异常,为了有效改善这个情况,MySQL 在 5.6.7 版本推出了Online DDL新特性。

我们常说的 Online DDL,其实是从 DML 操作的角度描述的,如果 DDL 操作不阻塞 DML,那么这个 DDL 就是 Online 的。

Online DDL中,也包含了copynplace两种方式,对于不支持Online DDLDDL SQL,则采用 COPY 方式;对于支持 Online DDL 的 DDL SQL,则采用 Inplace 方式,这里的 Inplace 又区分为 2 类:是否需要 rebuild 表格,判断标准为:是否修改行记录格式。

  • 如果修改了行记录格式,则需要 rebuild 表格,比如修改列类型、增减列等;
  • 如果没有修改行记录格式,仅修改表的元数据,则不需要 rebuild 表格,仅修改元数据 metadata,比如删除索引、设置默认值及重命名列名等。

它的过程和上面的 Copy 算法有些不同:

  1. Online DDL 过程中,从表 A 提取 B+树,并存储到一个中间文件 tmp-file,而不是中间表 tmp-A
  2. 步骤 1 执行过程中,对表 A 的写入,都会记录到 row log 中
  3. 步骤 1 执行完毕后,对 tmp-file 应用所有的 row log,得到一个与表 A 数据相同的数据文件
  4. 利用数据文件 tmp-file 替换表 A 的数据文件即可。

常用的 DDL 都默认使用这个算法。inplace 算法包含两类:inplace-no-rebuildinplace-rebuild,两者的主要差异在于是否需要重建源表。

存储引擎层"就地"重建表,虽然不阻塞 DML 操作,但对于大表变更可能导致长时间的主从不一致。对此类 DDL 语句,如果想使 DDL 过程更加可控,且对从库延迟比较敏感,建议使用第三方在线改表工具 ptosc 完成。

INSTANT DDL