ALTER TABLE ... ENGINE=InnoDB 不释放磁盘空间是正常行为,因仅整理页内碎片而不归还空闲空间给操作系统,需满足无长事务、innodb_file_per_table=ON等条件才可能收缩ibd文件。

ALTER TABLE ... ENGINE=InnoDB 为什么没真正释放磁盘空间
执行 ALTER TABLE t ENGINE=InnoDB 后,ibd 文件大小几乎不变——这不是 bug,是 InnoDB 的正常行为。它只是重建表、整理页内碎片,但不会把空闲空间还给操作系统,除非满足特定条件。
- 必须确保表没有长事务、未提交的 XA 事务或活跃的只读事务,否则 purge 线程无法清理 undo 日志,进而阻塞空间回收
-
innodb_file_per_table=ON是前提(MySQL 5.6+ 默认开启),否则所有表共用系统表空间,根本没法单独瘦身 - 执行后需等待
INFORMATION_SCHEMA.INNODB_METRICS中的buffer_pool_pages_free显著上升,且磁盘上ls -lh table.ibd大小才可能下降 - 若仍不缩,大概率是 undo log 或 change buffer 占着空间没释放,不是表本身的问题
OPTIMIZE TABLE 在高并发写入时会锁表多久
OPTIMIZE TABLE 在 MySQL 5.7+(含 8.0)对 InnoDB 表默认走 ALGORITHM=INPLACE,但「不锁表」是假象:它仍需获取 S(共享)MDL 锁,在 DDL 开始和结束各一次,中间阶段允许 SELECT/INSERT/UPDATE/DELETE,但遇到 DML 冲突会排队。
- 真实瓶颈常在 copy 阶段的 I/O 和 buffer pool 压力——大量页读入 + 重写,易触发
Buffer pool hit rate下降,连带拖慢其他查询 - 如果表上有全文索引、虚拟列或外键约束,
OPTIMIZE会自动退化为COPY算法,全程加X(排他)MDL 锁,等同于锁表 - 线上建议用
pt-online-schema-change --alter="ENGINE=InnoDB"替代,它能控制 chunk 大小和 sleep 时间,把影响摊薄
如何判断某张表是否真需要碎片回收
别看 Data_free 字段就动手——SHOW TABLE STATUS LIKE 't' 返回的 Data_free 是分配给该表但未使用的空间(单位字节),但它可能被 change buffer、undo log 或临时段占用,不代表可立即回收。
- 先查
SELECT DATA_LENGTH, DATA_FREE, TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='t',若DATA_FREE / DATA_LENGTH > 0.3且表长期有 DELETE/UPDATE,才值得介入 - 更准的方法是对比
innodb_buffer_pool_pages_data和innodb_buffer_pool_pages_total,如果前者远小于后者但ibd文件巨大,说明很多页是“逻辑空闲但物理未归还” - 用
innochecksum -v table.ibd | grep "fill factor"(需停机)看页填充率,低于 60% 才算严重碎片
TRUNCATE + INSERT 比 OPTIMIZE 更快但风险在哪
对冷表或可接受短时不可写场景,CREATE TABLE t_new AS SELECT * FROM t; DROP TABLE t; ALTER TABLE t_new RENAME TO t; 确实比 OPTIMIZE 快,因为跳过了 undo log 和 MVCC 版本链重建,但代价明确:
- 自增 ID 重置——如果业务依赖连续或单调递增的主键,这会导致下游同步异常或唯一键冲突
- 丢失表级元数据:如
COMMENT、ROW_FORMAT、KEY_BLOCK_SIZE不会自动继承,必须显式指定CREATE TABLE t_new LIKE t再INSERT INTO t_new SELECT * FROM t - 触发器、外键约束、分区定义全部失效,需人工重建;而
OPTIMIZE保留全部 DDL 元信息 - 如果表有
GENERATED COLUMN或JSON字段,SELECT *可能因隐式类型转换导致数据截断,务必验证
碎片回收不是“越勤越好”,InnoDB 的页分裂和合并本来就是常态。真正卡顿往往来自 buffer pool 不足、I/O 调度策略不当,或是单个大事务拖住 purge。先看 SHOW ENGINE INNODB STATUS\G 里的 History list length 和 LOG 段,再决定动不动表空间。











