必须分批删除且where条件字段需有索引;整表清空优先用truncate table,但存在外键、触发器或需保持自增id连续性时,须退回安全分批delete。

想删几百万行又不卡住业务?别直接 DELETE FROM table WHERE ...——它会锁死整段扫描范围,其他查询全堵住。必须分批删,且条件字段得有索引;要是整表清空,TRUNCATE TABLE 才是正解,但外键、触发器、自增ID连续性这三样里占一个,就得退回到分批 DELETE。
为什么直接 DELETE 大量数据会锁表
InnoDB 不是“删完再释放”,而是边扫边加锁:只要 WHERE 条件没走索引,就会全表扫描 + 文件排序,每行都加记录锁甚至间隙锁;哪怕只删 10 万行,也可能锁住上百万行的范围。其他事务一碰就等,SHOW PROCESSLIST 里全是 Updating 或 Waiting for table metadata lock。
- 错误现象:
ERROR 1205 (40001): Deadlock found when trying to get lock或连接超时 - 根本原因:事务太大,undo log 快速膨胀,binlog 写入延迟,主从同步直接掉队
- 关键约束:
ORDER BY id LIMIT N比OFFSET可靠,后者越往后越慢(MySQL 需跳过前 N 行) - 实操底线:每次删 1000–5000 行,删完
COMMIT,应用层加SLEEP(0.1)控制节奏
TRUNCATE 能不能用,先看这三个报错
TRUNCATE TABLE 瞬时完成,但它不是万能钥匙。一执行就报错?基本逃不出这三类:
-
ERROR 1701 (HY000): Cannot truncate a table referenced in a foreign key constraint→ 表被外键引用,需先SET FOREIGN_KEY_CHECKS = 0,删完立刻= 1 -
ERROR 1142 (42000): TRUNCATE command denied→ 账号缺DROP权限,不是DELETE权限,DBA 得补GRANT DROP ON db.table TO 'user'@'%' - 执行后
SELECT COUNT(*)是 0,但磁盘空间没缩?说明innodb_file_per_table = OFF,空间归还给共享表空间,OS 看不见——此时只能OPTIMIZE TABLE(低峰期),或迁移到独立表空间
分批 DELETE 的安全写法和参数取舍
别信“WHERE 1=1 就算带条件”的说法,那是反模式。真正可控的分批逻辑必须锚定有序主键或时间字段:
- 推荐写法:
DELETE FROM logs WHERE created_at ,前提是 <code>id或created_at有索引 - 避免写法:
DELETE FROM logs WHERE id IN (SELECT id FROM ...)—— 子查询在旧版 MySQL 容易锁全表,5.7+ 虽优化但仍不如直接范围删稳定 - 性能临界点:每批超 5000 行,InnoDB 可能升级为页锁;低于 500 行,网络往返和事务开销占比过高
- 注意:
DELETE后Auto_increment值不变,下条插入仍是原最大值 +1;若业务依赖 ID 连续(比如分库分表路由),得额外ALTER TABLE t AUTO_INCREMENT = 1
删完数据,磁盘空间为什么没回来
这是最常被忽略的一环:DELETE 和 TRUNCATE 都不会立刻把空间还给操作系统。InnoDB 只是把页标记为“可复用”,后续 INSERT 可能直接填进去,但文件大小纹丝不动。
-
TRUNCATE在innodb_file_per_table = ON下会删掉.ibd文件,空间立即释放;关了就只清数据,文件体积不变 -
DELETE后想真正瘦身,必须OPTIMIZE TABLE t或ALTER TABLE t ENGINE=InnoDB,但这两者都会重建表、加元数据锁,务必避开高峰期 - 监控依据:查
information_schema.TABLES的DATA_LENGTH和INDEX_LENGTH,别只看du -sh文件大小











