delete不加where会拖垮数据库,主因是无索引where导致全表扫描与行锁堆积,引发高负载和锁等待。

为什么 DELETE 不加 WHERE 会拖垮数据库
误删全表数据只是最坏结果,更常见的是没加索引的 WHERE 条件让 DELETE 变成全表扫描 + 行锁堆积。MySQL 5.7+ 默认用行级锁,但若条件字段无索引,引擎会退化为锁整张表(尤其在 READ-COMMITTED 隔离级别下),其他查询直接排队等锁,CPU 和 I/O 负载同步飙升。
实操建议:
- 执行前先
EXPLAIN DELETE FROM t WHERE x = ?(MySQL)或EXPLAIN (ANALYZE) DELETE FROM t WHERE x = ?(PostgreSQL),确认type是ref/range,不是ALL - 若
WHERE字段确实没索引,优先建索引再删;建索引本身也耗资源,需评估窗口期 - 避免在主库直接删千万级以上数据——考虑先在从库验证执行计划,再切回主库
低峰期 ≠ 随便选凌晨两点
“低峰期”不是时间刻度,是数据库真实负载水位。有些业务凌晨有定时归档、报表导出或第三方同步任务,此时 DELETE 可能撞上磁盘 I/O 高峰,反而放大延迟。
实操建议:
- 查过去 3 天的
SHOW PROCESSLIST快照或监控平台的innodb_row_lock_time_avg、pg_stat_bgwriter等指标,找连续 30 分钟以上QPS 且 <code>load 的时段 - 避开备份窗口(如
mysqldump或pg_basebackup正在运行时) - 对分库分表场景,别只看总 QPS——要逐库检查,某张冷表所在实例可能正被压测占用
删 10 万行和删 1000 万行,策略完全不同
单次大事务删除会撑爆 undo log、阻塞 purge 线程,还可能触发 MySQL 的 max_binlog_size 切割,造成主从延迟突增。PostgreSQL 则容易因 long transaction 导致 bloat 和 vacuum 压力。
实操建议:
- 超 10 万行务必分批:MySQL 用
LIMIT+ 循环,PostgreSQL 用WHERE ctid IN (SELECT ctid FROM t WHERE ... LIMIT 10000) - 每批间隔至少 1 秒,给 purge/vacuum 缓冲时间;用
SLEEP(1)比空循环更可控 - 记录已处理的最大 ID 或时间戳,避免漏删/重删;别依赖
OFFSET,越往后性能越差
监控不能只盯“成功与否”
命令返回 Query OK, 12345 rows affected 只代表语句结束,不代表系统已恢复。InnoDB 的 purge 线程可能还在后台清理死行,PostgreSQL 的 dead tuple 还没被 vacuum 扫到,后续查询仍可能变慢。
实操建议:
- 删完立刻看
SHOW ENGINE INNODB STATUS\G中的PURGE DONE进度,或 PostgreSQL 的pg_stat_progress_vacuum视图 - 观察接下来 10 分钟的
innodb_buffer_pool_pages_dirty是否回落,否则说明刷脏页压力大 - 对比删除前后同一条查询的
EXPLAIN ANALYZE输出,重点看actual time和rows removed by filter是否异常升高
真正麻烦的从来不是删的动作本身,而是删完那几分钟里,你没看到的后台线程正在拼命擦屁股。










