innodb的delete是标记删除而非物理擦除,仅置deleted_flag=1,不释放磁盘空间;真正收缩需optimize table或truncate等显式操作。

DELETE 只是标记删除,不是物理擦除
InnoDB 的 DELETE 本质是软删除:它把匹配行的 record header 中 deleted_flag 置为 1,并不移动数据页、也不归还磁盘块。哪怕你 DELETE FROM t WHERE 1=1 清空全表,.ibd 文件大小也纹丝不动。
这是因为 InnoDB 按 page(默认 16KB)管理存储,只要页里还有“可复用”空间,就不会主动释放整页给文件系统。Purge 线程后续可能清理这些标记,但仅限于腾出页内空间供新记录复用,和收缩文件无关。
- 插入 ID=5 的记录可能复用刚删掉的 ID=5 的位置,但插入 ID=8 就不行——空洞是稀疏且不可预测的
-
innodb_file_per_table = ON是前提,否则所有表共用ibdata1,连DROP TABLE都不缩主文件 - MyISAM 表同理,
DELETE后只更新 .MYD 中的删除位,不 shrink 文件
OPTIMIZE TABLE 是最常用收缩手段,但有代价
OPTIMIZE TABLE 对 InnoDB 实际执行的是 ALTER TABLE ... ENGINE=InnoDB(重建表),会申请新空间、拷贝有效行、丢弃空洞、最后原子替换原表。这是目前最直接有效的收缩方式。
但它会锁表(在 5.7+ 默认用 ALGORITHM=INPLACE 时仍需短暂排他锁;8.0 支持 ALGORITHM=COPY 外的并发 DML,但仍有性能抖动)。
- 别在业务高峰期跑:
OPTIMIZE TABLE orders;可能让查询排队数秒甚至分钟 - 对大表(比如 >50GB),建议搭配
pt-online-schema-change在线操作,避免停服 - 如果表含全文索引或虚拟列,
OPTIMIZE可能失败,得先查SHOW CREATE TABLE确认兼容性
TRUNCATE TABLE 能立即缩文件,但语义完全不同
TRUNCATE TABLE 是 DDL,会直接 DROP + CREATE 新表,瞬间释放全部空间并重置 AUTO_INCREMENT。它比 DELETE 快得多,也真缩文件。
但它不能带 WHERE,不能触发 DELETE 触发器,且事务中无法回滚(隐式提交)。
- 适合“清空整个历史日志表”这类场景,不适合“删掉 3 个月前的数据”
- 如果误删,
TRUNCATE没法像DELETE那样靠 binlog 闪回(除非开了binlog_format=ROW且保留了日志) - 执行前务必确认:该表没被外键引用,否则报错
ERROR 1701 (HY000)
真正要收缩,得跳出 DELETE 思维
靠 DELETE + 定期 OPTIMIZE 是被动救火。更可持续的做法是设计阶段就规避大表膨胀:
- 按时间分表(如
logs_202605/logs_202606),删旧表直接DROP TABLE,空间立减 - 用归档表 +
INSERT INTO archive SELECT ... FROM main WHERE ...+DELETE,再对主表OPTIMIZE - 监控
data_free字段:SELECT data_length, data_free FROM information_schema.tables WHERE table_name='t';,当data_free / data_length > 0.3就该干预了
空洞不是 bug,是 InnoDB 为写入性能做的权衡。想让它缩,就得明确告诉 MySQL:“我要重建”,而不是指望删完自动收拾残局。











