需先定位磁盘空间占用根源:tmpdir(排序文件)、datadir(表文件)或ibdata1(共享表空间),再针对性解决;tmpdir仅对copy算法有效且需满足路径权限、非tmpfs等条件;inplace算法不依赖tmpdir但需调大innodb_online_alter_log_max_size并控制并发;推荐pt-online-schema-change或分步清理数据,前提必须启用innodb_file_per_table=on。

直接改 tmpdir 不一定能解决问题,得先分清空间到底被哪部分吃掉了。
确认到底是 tmpdir、datadir 还是 ibdata1 撑爆了磁盘
报错 The table '#sql-xxx' is full 或 OS error code 28: No space left on device 看似统一,但根因完全不同:
- 如果
SELECT @@tmpdir;返回/tmp,且df -h /tmp显示使用率 95%+,尤其是df -T /tmp显示类型为tmpfs(内存挂载),那基本就是排序文件把内存当磁盘用了; - 如果
df -h显示/var/lib/mysql所在分区 100%,而du -sh /var/lib/mysql/*.ibd | sort -hr | head -3发现几个大表占了 90%+,说明是中间表(#sql-ib*.ibd)和原表共存导致——这需要至少 2 倍原表空间; - 如果
innodb_file_per_table = OFF,所有表挤在ibdata1里,哪怕你成功 ALTER,旧数据也不会释放,ibdata1只增不减,此时调tmpdir完全无效。
改 tmpdir 有效但有硬性前提
tmpdir 只对 COPY 算法生效(比如加全文索引、改 CHAR(255) 到 CHAR(500)),且必须同时满足:
- 路径本身有足够空闲空间(建议 ≥ 原表大小 × 1.5);
- MySQL 进程用户(如
mysql)对该路径有读写权限:chown mysql:mysql /data/tmp && chmod 750 /data/tmp; - 该路径不能是 tmpfs,必须是真实磁盘分区;
- 修改后需验证:
SET GLOBAL tmpdir = '/data/tmp';,再执行SHOW VARIABLES LIKE 'tmpdir';,并观察/data/tmp下是否出现#sql-*.ibd文件。
注意:innodb_tmpdir 和 tmpdir 是两回事——前者只管 InnoDB 临时表,后者才管 DDL 排序文件。
ALGORITHM=INPLACE 场景下 tmpdir 完全不生效
加普通二级索引、改列默认值这类操作走 INPLACE,不建临时表,也就根本不写 tmpdir。但它们仍要写 redo log 和 online DDL 日志,这部分空间由 innodb_online_alter_log_max_size 控制,默认仅 128MB。如果表很大、DML 很多,日志撑爆会直接报 DB_ONLINE_LOG_TOO_BIG。
- 调大该参数:
SET GLOBAL innodb_online_alter_log_max_size = 536870912;(512MB); - 但更关键的是控制并发:ALTER 期间尽量避免大批量 UPDATE/INSERT,否则日志增长不可控;
- INPLACE 不省空间——它仍需额外 undo/redo 空间,这些都在
datadir或innodb_log_group_home_dir下,无法通过tmpdir转移。
真正能绕过空间瓶颈的实操路径
别死磕单次 ALTER,优先用外部工具或分步策略:
- 用
pt-online-schema-change:它自己建影子表,路径可指定(--chunk-index+--database),完全脱离 MySQL 的tmpdir和datadir约束; - 先删历史数据再 ALTER:
DELETE FROM t WHERE created_at + <code>OPTIMIZE TABLE t;,缩小原表体积后再改结构; - 导出重建(离线窗口可用):
mysqldump -d db t > t.sql(只导结构),手动改完再mysqldump db t --where="1=1" > data.sql,然后DROP TABLE t;+ 导入,彻底规避中间状态。
最常被忽略的一点:innodb_file_per_table = ON 是所有空间回收操作的前提。如果它是 OFF,任何 OPTIMIZE、ALTER、DROP 都不会让 ibdata1 缩小——这点不确认清楚,后面所有操作都是白忙。











