必须先确认innodb_file_per_table=on,否则表在共享表空间ibdata1中,optimize或alter table无法缩容;两者本质均为重建表,但alter table更明确兼容性更好,大表建议用pt-online-schema-change替代。

确认表是否在独立表空间(innodb_file_per_table)
这是所有后续操作的前提。如果表还在共享表空间 ibdata1 里,OPTIMIZE TABLE 或 ALTER TABLE ... ENGINE=InnoDB 都无法缩小磁盘占用,只会整理内部页,而 ibdata1 永不缩容。
执行这条语句检查:
SELECT TABLE_NAME, TABLE_SCHEMA, CREATE_OPTIONS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'your_table';
同时确认全局参数:
SHOW VARIABLES LIKE 'innodb_file_per_table';
- 输出为
ON→ 表使用独立.ibd文件,可安全执行空间回收 - 输出为
OFF→ 所有 InnoDB 表都挤在ibdata1里,此时必须先导出数据、重装 MySQL 并开启该参数,再导入,否则无法释放空间
OPTIMIZE TABLE 和 ALTER TABLE ... ENGINE=InnoDB 怎么选
两者本质相同:都是重建表(复制数据+重建索引+生成新 .ibd),但行为细节有差异。
-
OPTIMIZE TABLE:对 MyISAM 效果最明显;对 InnoDB 会触发统计信息更新,但某些版本(尤其启用了innodb_strict_mode)可能报Table does not support optimize, doing recreate + analyze instead,实际仍是重建 -
ALTER TABLE your_table ENGINE=InnoDB:更明确、兼容性更好,不依赖优化器判断,且能绕过部分OPTIMIZE的隐式限制(比如某些压缩表场景) - 大表慎用:两者都会锁表(InnoDB 是锁写,但读不受影响),且需双倍磁盘空间临时存放新表。若表超 50GB,建议用
pt-online-schema-change替代
启用 ROW_FORMAT=COMPRESSED 前的关键约束
不是所有表都能直接压缩。MySQL 对压缩表有硬性限制,强行执行会报错:
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting blobs, is 65535.
原因在于压缩页(默认 KEY_BLOCK_SIZE=8)要求单行原始数据长度 ≤ 8126 字节(约 8KB)。常见踩坑点:
- 含多个
VARCHAR(2000)或TEXT字段的表,即使没存满,也可能因元数据开销超限 - 主键或二级索引包含长字段(如
VARCHAR(1000)),索引页本身也会受压缩限制影响 - MySQL 8.0.20+ 推荐改用
COMPRESSION='zstd'(需配合ROW_FORMAT=DYNAMIC),比老式COMPRESSED更灵活、支持在线 DDL
实操建议:先用 SELECT AVG(LENGTH(data)) FROM your_table; 粗估平均行大小,再决定是否启用压缩及选用多大的 KEY_BLOCK_SIZE(1/2/4/8/16,单位 KB)。
清理碎片后空间仍不释放?别只靠 DELETE
DELETE FROM table WHERE ... 只是标记删除,InnoDB 不会立刻归还磁盘空间给文件系统——它把空闲页保留在 .ibd 文件内,供后续插入复用。所以你看到 data_length 没变,不是 bug,是设计如此。
真正释放磁盘空间,必须触发物理重建:
- 删完立刻执行
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB - 如果表太大不敢一次性重建,分批
DELETE+ 每批后ANALYZE TABLE(更新统计信息,避免执行计划劣化),最后再整表重建 - 注意:
TRUNCATE TABLE虽快,但它是 DDL 操作,会直接 drop + recreate 表,等效于重建,也能彻底释放空间——但不可回滚,且会重置 AUTO_INCREMENT
最容易被忽略的是:即使做完所有操作,.ibd 文件变小了,操作系统层面的磁盘使用率可能延迟刷新。用 ls -lh table_name.ibd 看文件大小,比 df -h 更准。











