DELETE仅逻辑删除不释放空间,需VACUUM(PG)或OPTIMIZE TABLE(MySQL)回收;大表清空优先TRUNCATE;跨表删除须确保关联字段有索引,否则性能极差。

DELETE 语句删不掉空间?先确认是否真删了数据
很多情况下执行 DELETE FROM table_name WHERE ... 后,磁盘占用没变——不是语句没生效,而是数据库只标记了行“逻辑删除”,没真正释放物理空间。尤其在 PostgreSQL、MySQL(InnoDB)里,DELETE 不会自动回收数据页,旧记录还躺在那里占着位置。
实操建议:
- 用
SELECT COUNT(*)和pg_total_relation_size('table_name')(PostgreSQL)或SHOW TABLE STATUS LIKE 'table_name'(MySQL)对比删前删后,确认行数减少但大小不变 → 这就是典型“未释放空间”现象 - 避免在大表上直接
DELETE后就以为完事;尤其别用DELETE * FROM清空全表——效率低、锁表久、日志暴涨 - 如果只是清空整张表,优先用
TRUNCATE TABLE(注意:不可回滚、重置自增ID、不触发触发器)
MySQL InnoDB 下 DELETE 后必须 OPTIMIZE TABLE?
是的,但得看场景。InnoDB 的 OPTIMIZE TABLE 实际执行的是 ALTER TABLE ... FORCE,会重建表、整理碎片、更新统计信息。但它会锁表(8.0+ 支持在线 DDL,但仍有额外开销),且对超大表可能耗时几十分钟甚至更久。
实操建议:
- 小表(OPTIMIZE TABLE table_name 是稳妥选择
- 中大表(>10GB):改用
ALTER TABLE table_name ENGINE=InnoDB(效果等同,但语义更明确);生产环境务必在低峰期操作,并监控innodb_buffer_pool_wait_free是否飙升 - 如果表有全文索引或虚拟列,
OPTIMIZE可能失败,先查SHOW WARNINGS看具体错误,比如ER_FIL_SYSTEM_TABLESPACE_FULL - 别在从库上盲目执行——主从延迟可能加剧,且从库的
relay_log会剧增
PostgreSQL 中 VACUUM 和 VACUUM FULL 的关键区别
VACUUM 是日常维护操作,只清理死亡元组、更新可见性映射(VM)、允许事务重用空间;VACUUM FULL 才真正收缩物理文件大小,但会锁表、复制全部有效数据到新页、再替换原表——相当于一次强制重组。
实操建议:
- 常规清理后,先跑
VACUUM (VERBOSE, ANALYZE) table_name,看输出里pages removed和pages saved是否显著 → 判断是否真有空间可回收 - 只有当
pg_total_relation_size明显大于pg_table_size + pg_indexes_size之和时,才考虑VACUUM FULL -
VACUUM FULL期间无法写入,且临时需要 1.5 倍原表空间(因为要建新副本),磁盘不足会直接报错ERROR: could not extend file - 替代方案:用
CREATE TABLE new_table AS SELECT * FROM old_table WHERE ...+DROP/RENAME组合,可控性更强,还能顺便加CLUSTER排序
跨表关联删除时,WHERE 条件漏索引导致慢到卡死
比如想删掉用户表中所有无订单记录的用户:DELETE u FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.user_id IS NULL。如果 orders.user_id 没索引,MySQL 会为每一行 users 全表扫描 orders,10万用户 × 100万订单 = 百亿次扫描。
实操建议:
- 执行前先用
EXPLAIN看执行计划,重点确认type是ref或eq_ref,不是ALL;key列显示用了哪个索引 - 删前补索引:比如
ALTER TABLE orders ADD INDEX idx_user_id (user_id),哪怕临时加,也比硬扛慢查询强 - 分批删更安全:用
DELETE FROM users WHERE id IN (SELECT id FROM users WHERE ... LIMIT 1000)循环执行,配合SLEEP(0.1)避免长事务阻塞 - PostgreSQL 注意:子查询里不能直接引用外层表别名,得用
NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)写法,否则报错ERROR: invalid reference to FROM-clause entry
真正麻烦的从来不是删数据本身,而是删完之后没人检查 autovacuum 是否跟得上、binlog/归档日志有没有撑爆磁盘、以及那个被你顺手删掉的外键约束,其实还在另一个服务里硬编码着 ID 范围校验。

















