直接delete会卡死、锁表、拖垮主从,因其逐行扫描、写undo/redo日志、更新所有索引、逻辑删除后延迟清理空间,导致buffer pool占用高、刷盘频繁、行锁堆积甚至升级为表级锁;千万级数据下主库一删,从库sql线程串行重放即严重延迟。

为什么直接 DELETE 会卡死、锁表、拖垮主从?
因为 DELETE FROM t WHERE ... 不是“删一行释放一行”,而是:扫描全表(或索引范围)→ 对每条匹配行写 undo log 和 redo log → 更新所有相关索引 → 标记为逻辑删除 → 提交后才真正清理空间。千万级数据下,这等于持续占用 Buffer Pool、疯狂刷盘、锁住大量行甚至升级为表级锁,主库一删,从库同步立刻掉队。
分批删除用 LIMIT 但别用固定休眠
常见错误是写死 SLEEP(0.1) 或循环里无条件 COMMIT。实际要关注两个关键点:
- 每次
DELETE ... LIMIT N后必须检查ROW_COUNT(),为 0 才退出,否则可能漏删(尤其条件字段有重复值或索引不覆盖时) - 休眠不是为了“让数据库喘气”,而是避免压垮从库复制线程;建议用
SELECT SLEEP(0.05)而非客户端休眠,保证事务间隙可控 -
LIMIT值不宜过大:500–2000 较稳妥;超 5000 容易触发长事务告警,且单次锁持有时间过长
删前干掉索引,删完再建
索引越多,DELETE 越慢——每删一行,InnoDB 都得更新每个二级索引的 B+ 树叶子节点。实测某 8000 万行带 5 个二级索引的表,删 200 万旧数据耗时 6 小时;先 DROP INDEX idx_created_at ON t,删完再 CREATE INDEX,总耗时压到 18 分钟。
注意:DROP INDEX 不锁表(MySQL 5.7+),但重建索引会阻塞写入,务必在低峰执行;若表有唯一约束,不能直接删唯一索引,需改用 ALTER TABLE ... DROP INDEX + ADD UNIQUE INDEX 组合操作。
删多留少时,别碰 DELETE,用 CREATE TABLE ... LIKE + RENAME TABLE
当你要删掉 90% 以上数据(比如 1.6 亿删剩 250 万),DELETE 是最差选择。正确路径是:
- 先
CREATE TABLE t_new LIKE t,确保索引、字符集、分区定义全部一致 - 用
INSERT INTO t_new SELECT * FROM t WHERE ...拉取保留数据(加ORDER BY id可减少页分裂) - 用
RENAME TABLE t TO t_old, t_new TO t原子切换——毫秒级,业务无感 - 最后
DROP TABLE t_old,后台异步清理
这个方案真正难的不是 SQL,而是确认“保留条件”是否能走索引、是否覆盖全部业务路径;漏一条 WHERE 条件,就等于把脏数据直接切进线上。











