MySQL8.0大表秒加字段,是如何实现的?

1,282 阅读5分钟

前言

在MySQL 5.7已经之前的版本,MySQL还未实现大表加字段进行优化,以往的做法我们都是采用在线ddl工具去实现,比如:pt-online-schema-change,且需要在访问量少的时候去执行(比如凌晨),但是在 MySQL8.0 支持快速加列,可以实现大表秒级加字段。本篇文章我们就一起来看下 MySQL8.0 快速加列到底要如何操作。

MySQL8.0参考手册:dev.mysql.com/doc/refman/…

了解背景信息

表结构的变更是业务运行过程中比较常见的需求之一,在 MySQL 的环境中,可以使用 Alter 语句来完成这些操作,这些 Alter 语句对应的操作通常也称之为 DDL 操作。通常情况下大表的 DDL 操作都会对业务有很明显的影响,需要在业务空闲,或者是维护的时候做。MySQL 5.7 支持 Online DDL,大部分 DDL 不影响对表的读取和写入,但是依然会消耗非常多的时间,且占用额外的磁盘空间,并会造成主从延迟。所以大表 DDL 仍是一件令 DBA 头痛的事。

听闻 MySQL 8.0 解决了这件令 DBA 头痛的事,那让我们来详细了解下吧。想了解新功能,最简单的方法就是查阅官方文档。查阅官方文档得知,快速加列即 Instant Add Column ,该功能自 MySQL 8.0.12 版本引入,是由腾讯游戏DBA团队贡献。注意一下,此功能只适用于 InnoDB 表。

快速加字段测试

快速加列采用的是instant算法,使得添加列时不再需要rebuild整个表,只需要在表的metadata中记录新增列的基本信息即可。在ALTER语句后增加ALGORITHM=INSTANT即代表使用 instant 算法, 如果未明确指定,则支持 instant 算法的操作会默认使用。如果ALGORITHM=INSTANT指定但不支持,则操作立即失败并显示错误。

下表概述了对列操作的在线 DDL 支持。星号表示附加信息、异常或依赖项:

操作InstantIn PlaceRebuilds Table允许并发DML仅修改元数据
添加列Yes*YesNo*Yes*No
删除列NoYesYesYesNo
重命名列NoYesNoYes*Yes
更改列顺序NoYesYesYesNo
设置列默认值YesYesNoYesYes
更改列数据类型NoNoYesNoNo
扩展VARCHAR列大小NoYesNoYesYes
删除列默认值YesYesNoYesYes
更改自动增量值NoYesNoYesNo
设置列为nullNoYesYes*YesNo
设置列not nullNoYes*Yes*YesNo
修改ENUM/SET列的定义YesYesNoYesYes

语法和使用说明:

添加列:

ALTER TABLE *tbl_name* ADD COLUMN *column_name* *column_definition*, ALGORITHM=INSTANT;

INSTANT是 MySQL 8.0.12 和INPLACE之前的默认算法。

ALTER TABLE ... ALGORITHM=INSTANT在每次添加列、删除列或在同一操作中添加和删除一或多列的操作之后,都会创建一个新的行版本,该INFORMATION_SCHEMA.INNODB_TABLES.TOTAL_ROW_VERSIONS列跟踪表的行版本数。每次立即添加或删除列时,该值都会增加。初始值为0

INSTANT算法使用最广泛的应该是添加列了,可以看到使用该算法还是有些限制的,一些限制如下:

  • 如果ALTER语句包含了ADD COLUMN和其他的操作,其中有操作不支持INSTANT算法的,那么ALTER语句会报错,所有的操作都不会执行。
  • 只能顺序加列, 仅支持在最后添加列,而不支持在现有列的中间添加列。
  • 不支持压缩表,即该表行格式不能是 COMPRESSED。
  • 不支持包含全文索引的表。
  • 不支持临时表。
  • 不支持那些在数据字典表空间中创建的表。

说的再多不如实际来测下,下面我们以 8.0.19 版本为例来实际验证下:

这里采用PolarDB-Sysbench生成一张1000W的大表test1,关于Sysbench使用指南可以参考:help.aliyun.com/document_de…

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.19    |
+-----------+
1 row in set (0.00 sec)

mysql> select count(*) from test1;
+----------+
| count(*) |
+----------+
| 10000000 |
+----------+

# 增加无默认值的列
mysql> alter table test1 add column col1 varchar(20), algorithm=instant;
Query OK, 0 rows affected (0.63 sec)
Records: 0  Duplicates: 0  Warnings: 0

# 增加有默认值的列
mysql> alter table test1 add column create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', algorithm=instant;
Query OK, 0 rows affected (0.58 sec)
Records: 0  Duplicates: 0  Warnings: 0

# 不显式指定instant算法
mysql> alter table test1 add column col2 varchar(20);
Query OK, 0 rows affected (0.55 sec)
Records: 0  Duplicates: 0  Warnings: 0

# 设置列的默认值
mysql> alter table test1 alter column col1 set default 'sql',algorithm=instant;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

# 指定In Place算法添加列,(5.7版本添加列使用该算法)
mysql> alter table test1 add column col_inplace varchar(20),algorithm=inplace;
Query OK, 0 rows affected (1 min 23.30 sec)
Records: 0  Duplicates: 0  Warnings: 0
复制代码

通过以上测试,我们可以发现,使用INSTANT算法添加列基本都在 1s 内完成,对于大表来说这个速度是非常快的,业务基本无感知。当使用 5.7 版本的inplace算法时,则添加列的时间上升至数分钟。对比看来 8.0 版本的快速加列功能确实非常实用!

总结:

虽然快速加列存在一些限制, INSTANT算法也只适用于部分 DDL 操作,但 8.0 的这项新功能已经足以令人兴奋,很大程度上解决了大表加字段的大难题。通过这篇文章,希望各位能了解到这项新功能,是不是有想升级到 8.0 的冲动了呢,可以着手准备起来了。