根本原因是多条DELETE在单事务中顺序执行时前条未提交,后条因Next-Key Lock锁住重叠索引范围而阻塞;需分小事务、确保WHERE走覆盖索引、按固定顺序操作表并避免全表扫描。

为什么多条 DELETE 会卡在锁等待上?
根本原因不是 DELETE 本身慢,而是事务里多条 DELETE 按顺序执行时,前一条没提交,后一条又去删同一张表里相邻或重叠的索引范围(尤其是 WHERE 条件走非唯一索引),就会触发间隙锁(Gap Lock)或临键锁(Next-Key Lock),导致后续语句被阻塞。
常见现象:SHOW PROCESSLIST 看到状态是 Waiting for table metadata lock 或更典型的是 Updating 卡住,而 SELECT * FROM information_schema.INNODB_TRX 显示事务长时间 RUNNING,且 trx_state = 'LOCK WAIT'。
- MySQL 默认隔离级别
REPEATABLE READ下,DELETE会加临键锁,不只是行锁 - WHERE 条件没走索引?那整张表都会被扫描并加锁,后果更严重
- 批量删 10 万行,但写成 100 条单行
DELETE在一个事务里?锁会累积,不释放
怎么让多条 DELETE 不互相等锁?
核心思路是减少锁持有时间 + 避免锁冲突范围重叠。别指望“优化单条 DELETE”,重点在事务结构和语句组织。
- 拆成多个小事务:每 500~1000 行一个
COMMIT,用WHERE id BETWEEN ? AND ?分片,避免大范围 gap 锁扩散 - 确保所有
DELETE的WHERE条件都命中同一个**覆盖索引**,最好是主键或唯一索引;否则优化器可能选错索引,锁住不该锁的间隙 - 如果必须在一个事务里删多张表,按**固定顺序**操作表(比如总是先删
order_item,再删order),防止死锁 - 显式加锁控制:对关键行先
SELECT ... FOR UPDATE,再DELETE,比直接DELETE更可控(但注意这也会延长锁时间)
用 LIMIT 限制单次 DELETE 行数有用吗?
有用,但只在特定条件下生效——必须配合 ORDER BY 和索引,否则 LIMIT 可能被优化器忽略锁范围。
例如:DELETE FROM t WHERE status = 0 LIMIT 1000,如果 status 没索引,MySQL 仍会扫描全表并为所有匹配行加锁,LIMIT 只影响最终删几行,不减少锁数量。
- 安全写法:
DELETE FROM t WHERE id IN (SELECT id FROM t WHERE status = 0 ORDER BY id LIMIT 1000)(MySQL 8.0+ 支持,需确保id是主键) - 或者更稳妥:
DELETE FROM t WHERE status = 0 AND id ,每次取上一批最大 <code>id作为下限 -
innodb_lock_wait_timeout别设太高(默认 50 秒),否则卡住太久才报错,掩盖真实瓶颈
如何快速定位哪条 DELETE 在等谁的锁?
别翻日志,直接查 INFORMATION_SCHEMA 视图组合分析。
- 查当前锁等待:
SELECT * FROM performance_schema.data_lock_waits(MySQL 8.0+)或老版本用SELECT * FROM information_schema.INNODB_LOCK_WAITS - 关联查阻塞者:
SELECT b.trx_id, b.trx_mysql_thread_id, b.trx_query FROM information_schema.INNODB_TRX b WHERE b.trx_id = 'xxx'(把等待者blocking_trx_id填进去) - 看锁类型:
SELECT * FROM performance_schema.data_locks WHERE ENGINE = 'INNODB',重点关注LOCK_MODE是X,GAP还是X,REC_NOT_GAP
真正麻烦的不是锁本身,而是多个业务逻辑共用同一张表、同一类索引条件删数据,却没约定分片策略或时间错峰——这种设计层面的耦合,靠调参解决不了。

















