用where id between易卡住其他查询,因其触发间隙锁覆盖不可预测范围,导致并发事务锁重叠形成循环等待;正确做法是where id > last_id order by id limit n配合联合索引与应用层休眠。

为什么WHERE id BETWEEN会卡住其他查询
用 WHERE id BETWEEN 10000 AND 20000 删除时,InnoDB 不只锁住命中的行,还会对范围内的间隙加 gap lock。如果并发事务删的是 19999 AND 29999,两个事务的间隙锁重叠,就容易形成循环等待。更麻烦的是,id 不连续或存在新插入数据时,间隙覆盖区域不可预测,锁范围可能远超预期。
分批删必须带 ORDER BY + 单调字段锚点
只写 DELETE FROM t WHERE status = 'archived' LIMIT 1000 是危险的:InnoDB 不保证扫描顺序,同一批可能反复删同一组行,漏掉另一些;尤其在有并发 INSERT 的场景下,id 增长但没被锚定,逻辑就乱了。
- 正确写法是
DELETE FROM t WHERE status = 'archived' AND id > 12345 ORDER BY id LIMIT 1000 -
last_id必须来自上一批实际删除的最大id值(不是SELECT MAX(id)全表查) - 若删完发现没命中的行,说明
last_id已越界,需用SELECT id FROM t WHERE status = 'archived' ORDER BY id LIMIT 1重新找起点
索引没建对,分批也白忙
执行 EXPLAIN DELETE FROM t WHERE status = 'archived' AND id > 12345 ORDER BY id LIMIT 1000,如果 type 是 ALL 或 index,说明没走索引——哪怕写了 WHERE 和 LIMIT,照样全表扫描加锁。
- 别建单列
INDEX(status),低基数字段单独索引效果差 - 应建联合索引
INDEX(status, id),确保过滤 + 排序都能命中最左前缀 - 避免在
WHERE中做函数操作,比如DATE(created_at) = '2024-01-01'会让索引失效
不加 sleep 的分批删,反而更伤业务
每批删完立刻执行下一批,MySQL 线程调度密集,CPU 和 binlog 写入压力集中,其他事务排队等锁释放的时间反而更长。从库重放压力也会陡增,Seconds_Behind_Master 容易暴涨。
- 应用层控制休眠,推荐
sleep(0.1)~sleep(0.5),机械盘可拉到sleep(1) - 绝对不要用
SELECT SLEEP(0.1),它计入事务时间,延长锁持有 - 监控
Innodb_row_lock_time_avg和Com_delete增速,判断节奏是否合理











