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成功了”代替容量预算已经正确。