MySQL 在 AWS RDS 下添加列速度慢

MySQL ADD COLUMN slow under AWS RDS

我有一个 RDS MySql,设置如下:

然后我想向大小约为 20 GB 的 table 添加几列(根据 INFORMATION_SCHEMA.files)。这是我的声明:

ALTER TABLE MY_TABLE
ADD COLUMN NEW_COLUMN_1 DECIMAL(39, 30) NULL,
ADD COLUMN NEW_COLUMN_2  DECIMAL(39, 30) NULL,
ADD COLUMN NEW_COLUMN_3 INT(10) UNSIGNED NULL,
ADD CONSTRAINT SOME_CONSTRAINT FOREIGN KEY (NEW_COLUMN_3) REFERENCES SOME_OTHER_TABLE(SOME_OTHER_PK),
ADD COLUMN NEW_COLUMN_4 DATE NULL;

执行此查询需要 172 分钟。大部分时间都花在了将数据处理到临时 table.

在该操作期间,没有执行其他查询(读取或写入)。我有自己的数据库。 SHOW FULL PROCESSLIST 是说 State 等于 copy to tmp table 我的查询。

我不明白的是 AWS RDS 控制台告诉我写入吞吐量在 30 MB/s 和 35 MB/s 之间持续 172 分钟。

假设写入吞吐量为 30 MB/s,我应该能够写入 30 * 60 * 172 = 309600 MB = 302 GB。这比操作期间创建的临时 table 的大小 (20 GB) 大得多。

所以两个问题:

  1. mysql/rds 在我的 table 旁边写了什么?有没有办法禁用它,以便我可以获得全部带宽来创建临时文件 table?
  2. 有什么方法可以加速该操作吗?写20GB的数据用3个小时,好像很长。

InnoDB 可能是您正在使用的存储引擎,因为它是默认的存储引擎。 InnoDB 做了一些看似多余的 I/O,以确保没有数据丢失。

例如:

  • 缓冲池中修改的数据和索引页必须写入table空间。 table在添加列的过程中可能需要拆分一些页面,因为行变得更宽,每页适合的行数更少。
  • 在向 table 空间写入页面时,InnoDB 首先将这些页面写入双写缓冲区,以确保在页面写入过程中发生崩溃时不会丢失数据。
  • 事务写入 InnoDB 重做日志,这甚至可能导致多次覆盖日志中的同一块。
  • 如果出于复制目的启用二进制日志,事务也会写入二进制日志。虽然这在 ALTER TABLE 语句的转换中应该不是很大的成本,因为 DDL 语句总是以语句格式而不是行格式写入二进制日志。

您还询问了可以做些什么来加速 ALTER TABLE。希望它 运行 更快的原因通常是因为在 ALTER TABLE 期间,table 被锁定并可能阻止并发查询。

在我的公司,我们使用免费工具 pt-online-schema-change,因此我们可以在 table 被更改的同时或多或少地自由使用它。以这种方式完成更改实际上需要更长的时间,但它并没有那么不方便,因为它不会阻止我们访问 table.

我使用的是 MySQL 5.7。根据此MySQL blog post,8.0 版改进了这种情况:“InnoDB 现在支持 Instant ADD COLUMN”。

因此我更改了查询以使用新功能。

-- Completes in 0.375 seconds!
ALTER TABLE MY_TABLE
ADD COLUMN NEW_COLUMN_1 DECIMAL(39, 30) NULL,
ADD COLUMN NEW_COLUMN_2  DECIMAL(39, 30) NULL,
ADD COLUMN NEW_COLUMN_3 INT(10) UNSIGNED NULL,
-- 'ALGORITHM=INSTANT' is not compatible with foreign keys. 
-- The foreign key will need to be added in another statement
-- ADD CONSTRAINT SOME_CONSTRAINT FOREIGN KEY (NEW_COLUMN_3) REFERENCES SOME_OTHER_TABLE(SOME_OTHER_PK),
ADD COLUMN NEW_COLUMN_4 DATE NULL, 
-- the new option
ALGORITHM=INSTANT;

-- This completed in about 6 minutes. 
-- Adding the foreign creates an index under the hood. 
-- This index was 1.5 GB big.
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE MY_TABLE
  ADD FOREIGN KEY (NEW_COLUMN_3) REFERENCES SOME_OTHER_TABLE(SOME_OTHER_PK);
SET FOREIGN_KEY_CHECKS=1;

所以我的结论是:

  • 升级到 MySQL 8 如果可以
  • 确保您始终(如果可能)使用 ALGORITHM=INSTANT 选项。