加了索引的delete仍锁住大片范围,因rr级别下走非唯一索引时默认加next-key lock,锁定索引扫描路径上的记录及左侧间隙,范围由索引树前驱节点决定而非where条件值。

直接结论:锁等待不是 DELETE 本身的问题,而是其他事务没释放锁、或你的 DELETE 锁范围过大导致的。优先查阻塞源,再优化语句和事务。
怎么快速定位谁在挡路
执行 SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT' 找出正在等锁的事务;拿到它的 TRX_ID,再去查 INNODB_LOCK_WAITS,就能得到 BLOCKING_TRX_ID;最后用这个 ID 查 INNODB_TRX 的 TRX_QUERY 和 TRX_STARTED —— 如果 TRX_QUERY 是空的,说明那个事务早就 START TRANSACTION 了但一直没提交,很可能是应用层异常挂起。
常见干扰项:
-
TRX_ROWS_LOCKED超过几万行,基本可判定是长事务或未提交的批量操作 -
TRX_WAIT_STARTED和当前时间差超过 30 秒,说明卡很久了,大概率要人工干预 - 不要只看
SHOW PROCESSLIST,它不显示锁持有关系,容易漏掉“静默持锁者”
为什么加了索引 DELETE 还会锁住一大片
MySQL 在 REPEATABLE READ 隔离级别下,DELETE 走二级索引时默认加 Next-Key Lock(记录锁 + 间隙锁),哪怕你只删一行,也可能锁住整个范围。比如 WHERE date 用了 <code>KEY idx_date(date),InnoDB 就会锁住所有 date 小于该值的间隙,防止插入新记录造成幻读。
关键点:
- 如果
WHERE条件没走索引(比如隐式类型转换、函数包裹字段),就会全表扫描 → 锁所有行 - 复合索引中跳过最左前缀(如索引是
(a,b,c),却用WHERE b = ?),优化器可能放弃走索引 - 即使走了索引,InnoDB 仍需回主键校验可见性,若主键分布稀疏,可能扩大锁区间
kill 线程前必须确认三件事
别一看到 KILL 就手抖执行。先确认:
- 被 kill 的线程是否来自核心业务(比如订单结算、支付回调),贸然中断可能引发数据不一致
- 该事务是否正在回滚(
TRX_STATE = 'ROLLING BACK'),此时 kill 会强制终止回滚,可能留下半清理状态 - 上游是否有重试机制?如果只是短暂超时就 kill,而应用层立刻重试,反而加剧锁竞争
真正安全的操作是:KILL 前先查 INNODB_TRX 的 TRX_MYSQL_THREAD_ID,再用 SHOW PROCESSLIST 看 Info 字段是否为空或明显非关键 SQL;若不确定,宁可调大 innodb_lock_wait_timeout(比如设为 120)争取排查时间。
分批删比硬扛更可靠
单条 DELETE WHERE date 如果匹配百万行,锁持有时间长、冲突概率高。改成按主键分页删更稳:
DELETE FROM rainbow_client_infos
WHERE id IN (
SELECT id FROM (
SELECT id FROM rainbow_client_infos
WHERE date <p>注意点:</p>
- 必须用
ORDER BY id+ 子查询包装,否则 MySQL 8.0+ 会报错 “You can't specify target table for update in FROM clause” - 每次删完检查影响行数,为 0 时停止循环
- 避免用
LIMIT直接删(如DELETE ... LIMIT 1000),它不保证顺序,可能反复扫同一块间隙,加重锁竞争
真正难处理的从来不是怎么删,而是怎么让别人不等你删——锁等待的本质是资源争抢,而最隐蔽的争抢源,往往藏在那些没人关注的长事务里。











