原创

MySQL 在线改表的空间预算:中间表、排序文件与并发日志

“数据盘还剩一张表那么大的空间,在线改表应该够了。”这份估算漏掉的往往不是一个小文件。排序临时文件、重建中的表、并发变更日志可能同时存在,而且未必落在同一块盘上。

MySQL 8.4 的在线 DDL 允许某些操作期间继续读写,不等于只在原文件里改几个字节。预算应该围绕具体算法与文件落点建立,不能把“在线”“INPLACE”“不重建”当作同一个意思。

先问这条 ALTER 有没有重建表

用一个完整小表固定讨论对象。只在实验库执行,它用于检查语句路径,不用于测量大表临时空间。

CREATE DATABASE IF NOT EXISTS ddlspace41_lab
  CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE ddlspace41_lab;

CREATE TABLE event_chunk (
    id BIGINT UNSIGNED NOT NULL,
    kind VARCHAR(32) NOT NULL,
    body VARBINARY(4096) NOT NULL,
    PRIMARY KEY (id),
    KEY idx_kind (kind)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

INSERT INTO event_chunk VALUES
(1,'upload',REPEAT('a',1024)),
(2,'download',REPEAT('b',1024));

ALTER TABLE event_chunk
ADD COLUMN source VARCHAR(32) NOT NULL DEFAULT 'import',
ALGORITHM=INPLACE, LOCK=NONE;

在8.4中,添加这个普通列本可考虑 INSTANT;此处故意要求 INPLACE,是为了讨论会重建表的数据路径。不能因为算法名字写着“原地”,就把新布局所需工作空间从预算里删掉。实际发布若只需这项加列,应先核对能否使用更轻的受支持算法,而不是照抄本例强制重建。

增加普通二级索引又是另一种路径:通常不用重建整张聚簇表,却需要读取数据、排序并建立新索引。同为 INPLACE,文件与 I/O 需求不完全相同;改主键、改列可空性等也各有条件。先按具体操作拆清楚,空间预算才有对象。

三类临时占用,不能都算在 tmpdir

占用 为什么出现 主要检查点
重建中间表 把有效行和索引组织成新布局 原表所在目录或相关表空间落点
排序临时文件 为索引构建等操作排序 innodb_tmpdir;未配置时的相关默认临时目录
在线变更日志 记录改表期间的并发 DML,供后续应用 对应在线操作的日志增长与上限

此外,原表在切换完成前不能简单从容量账里扣掉。其他业务写入、日志保留、复制相关文件和备份也可能同期增长。不同文件系统之间的空闲空间不能互相抵扣:临时盘富余,不会自动救活已经写满的数据盘。

连接到目标实例读取相关配置,而不是只看运维文档里某台旧机器的目录:

SELECT @@GLOBAL.datadir,
       @@GLOBAL.tmpdir,
       @@GLOBAL.innodb_tmpdir,
       @@GLOBAL.innodb_online_alter_log_max_size;
SHOW CREATE TABLE ddlspace41_lab.event_chunk;

innodb_tmpdir 可以为相关在线 DDL 排序临时文件提供不同位置,但它不是把此次 DDL 的所有写入都重定向过去的总开关。重建中间表仍有自己的位置,原表、redo、binlog也各有存储路径。改了临时目录以后,还应核对操作系统权限与实际文件系统容量,而非仅检查变量能否读出来。

容器与宿主机也要区分路径。容器内目录名不同,底层却可能挂在同一个宿主卷上;两个目录各看见足够可用容量,不代表拥有两份彼此独立的空间。预算应落到真实文件系统或配额边界,而不止列出几个路径字符串。

在线变更日志不是一份可以无限堆积的队列

并发业务继续修改目标表时,在线 DDL 需要记录这些变更,之后应用到新结构。innodb_online_alter_log_max_size 为相关日志设置上限;超过时,操作可能失败,不能指望它自动无限扩张到“反正磁盘还够”。

它也不是整个 MySQL 进程所有 DDL 共用的一份总额度。并行维护多个对象,需要分别考虑各操作的日志与临时资源,不能只给这个变量记一笔空间。调整上限还要考虑最终应用累积变更所需工作,允许积得更多,不等于收尾就更轻。

相反,调小上限也不保证降低总风险。目标表写入频繁,日志很快耗尽,维护可能在已经做了大量工作后失败。更合适的判断应包括业务写入速率、操作耗时及是否可以降低同期变更压力,而不是只观察开始时那一张空闲磁盘截图。

这个实验只有两条静态记录,不会主动制造高并发填满日志,更没有测得某个安全阈值。线上预算需要在接近真实数据与负载的隔离环境观察,并把峰值发生在哪个阶段保留下来。复制一条变量值不能代替这项证据。

按文件系统做峰值账,而不是按表大小做乘法

可以把预算拆为“当前已用”“此次操作峰值新增”“同期其他增长”“保留余量”四项。重建所需新布局不必和原文件逻辑长度相同,原表里的空闲页、数据分布、索引定义与目标结构都会改变结果。排序文件又可能有另一份峰值,不能假定它一定在新表开始增长前就已经删除。

真正需要回答的是:同一时刻,这块盘上哪些文件必须共存。若只把每一阶段的单独最大值相加,可能过度保守;若把它们完全不重叠,又可能低估。可以按准备、构建、应用并发变更、提交切换、清理几个阶段标记占用,再使用实际观察修正共存关系。

对于共享表空间,还应考虑已经释放的内部空间是否能复用,以及表空间文件是否会向操作系统缩小。重建完成后的内部空闲,与执行中是否还有足够文件系统空间,是不同问题。不能把“最后能够复用”提前算成当前已经可用的磁盘容量。

测量表规模时,INFORMATION_SCHEMA.TABLES 的 DATA_LENGTH、INDEX_LENGTH等属于有具体口径的统计,不能原样当作下一次重建承诺。操作系统上的文件长度与实际分配也可能不同,使用稀疏或压缩存储时尤其需要写清单位。

ALTER TABLE 会隐式提交,执行前应先结束同连接中的业务事务。InnoDB 的原子 DDL 解决结构变更自身的提交或恢复一致性,不等于操作者能在成功改表后用 ROLLBACK 撤销;这两项能力不能放在同一条事务承诺里。

失败后的空间释放也有过程

磁盘或日志限制导致 DDL 失败时,服务器还需要清理本次操作的工作。看到客户端已经报错,不意味着所有临时占用同一瞬间都消失。终止操作也不能替代数据库自己的恢复与清理过程;手工删除看起来像临时表的文件,会把一个可处理的资源问题变成数据字典问题。

因此维护窗口不仅要容纳成功路径,也要留出取消、回滚与观察恢复的时间。失败后先确认目标结构、后台操作状态与文件变化,再决定重试。使用新连接重新发同一条 ALTER 前,还应先查列或索引是否已经存在,避免把提交响应不确定误判为完全未执行。

对示例表,可以最后确认 source 列、数据数量与索引定义:

SHOW CREATE TABLE ddlspace41_lab.event_chunk;
SELECT COUNT(*) AS rows_after,
       MIN(source) AS min_source,MAX(source) AS max_source
FROM ddlspace41_lab.event_chunk;

两条记录应保留,source 都为 import。这个结果验证结构变更的业务效果,临时空间峰值则需要另一份按路径和阶段记录的证据。把两份结果分别保存,才不会用“ALTER成功了”代替容量预算已经正确。

正文到此结束
评论插件初始化中...
Loading...