直接用DELETE FROM t WHERE create_time < '2025-01-01' LIMIT 1000易锁超时,因未加索引时全表扫描+间隙锁、LIMIT无ORDER BY致重复扫描,且offset递增使扫描开销剧增;应改用主键范围(如id > last_id)分批删。

为什么用 LIMIT 分批删大表反而更容易锁超时
直接写 DELETE FROM t WHERE create_time 看似分批,但每次执行仍要全表扫描匹配条件——如果 <code>create_time 没索引,InnoDB 就得逐行判断、逐行加锁、逐行更新所有二级索引。1000 行不是“只锁 1000 行”,而是可能锁住几万行(间隙锁+主键锁),还拖着 undo log 和 redo log 不停写。结果就是:每批都卡在等锁,下一批刚启动就撞上上一批没释放的锁,Lock wait timeout exceeded 频发。
真正有效的分批删除必须带游标式 WHERE 条件
核心是让每次 DELETE 都能走索引、范围可控、不回表、不跳过大量无效行:
- 确保
WHERE字段(如id或create_time)有**单列或复合索引**,且该字段值单调递增/递减 - 第一轮删:
DELETE FROM t WHERE create_time - 立刻查出本次删掉的最大
id:SELECT MAX(id) FROM t WHERE create_time - 下一轮把
id 换成 <code>id ,持续推进 - 应用层每次执行后加
SLEEP(0.1),避免 IOPS 打满、阻塞其他查询
LIMIT 加 OFFSET 是性能黑洞,别碰
写成 DELETE FROM t WHERE create_time 这类语句,MySQL 内部仍要先定位前 100000 行再删后面 1000 行——OFFSET 越大,扫描越深,锁住的间隙越多,超时概率指数级上升。这不是分批,是自找死路。
删完不 OPTIMIZE TABLE,空间不会回收
InnoDB 删除只是标记为可复用,物理空间不释放。碎片堆积会导致后续查询变慢、索引效率下降。但 OPTIMIZE TABLE 本身会锁表、重建聚簇索引,在大表上是重量级操作。它不能和删除混在同一窗口执行,必须单独安排低峰期,并确认业务能接受短时不可写。


















