根本原因是innodb默认将无法立即获取锁的事务加入等待队列而非快速失败,配合50秒默认超时与连接池限制,导致连接耗尽;无索引条件还会引发全表扫描并锁住大量记录。

SELECT ... FOR UPDATE 在高频率竞争下不是“慢”,而是会直接卡住大量连接,线程积压本质是锁等待队列膨胀 + 连接池耗尽。
为什么 SELECT ... FOR UPDATE 会排队等锁而不是快速失败
InnoDB 默认不主动拒绝锁请求,而是把等待中的事务放进锁等待队列。只要没超时,就一直挂着——这和应用层的线程池“排队等 worker”逻辑一样,但更隐蔽。
-
innodb_lock_wait_timeout默认是 50 秒,意味着一个抢不到锁的请求会空耗连接 50 秒才报错Lock wait timeout exceeded - 应用若没设连接超时(比如 JDBC 的
socketTimeout),这个等待还会叠加网络层阻塞 - 每个等待事务都占着一个数据库连接,而连接池大小通常远小于并发请求数
行锁升级成“事实表锁”的常见诱因
你以为只锁几行,实际可能锁住整个索引范围,尤其在无合适索引或范围查询时。
- 没走索引的
WHERE条件 → 全表扫描 → 所有聚簇索引记录被逐个加锁(或触发间隙锁) UPDATE t SET x=1 WHERE id > 100 AND id → InnoDB 对 (100, 200) 区间加间隙锁,新插入 id=150 的记录也会被阻塞- 唯一索引失效(比如对
name字段查LIKE '%abc')→ 退化为全索引扫描 → 锁住所有匹配项及间隙
应用层没做重试或降级,导致雪崩式积压
死锁回滚后抛出 Deadlock found when trying to get lock,但很多业务代码直接往上扔异常,既不捕获重试,也不 fallback 到乐观锁或缓存兜底。
- 一次死锁触发后,若客户端重试逻辑缺失,用户请求直接失败;若重试无退避,反而加剧竞争
- 多个服务共用同一张热点表(如库存扣减表),各自按不同字段查再
FOR UPDATE,锁顺序不一致 → 死锁率飙升 - 没用
FOR UPDATE SKIP LOCKED(MySQL 8.0+)处理队列类场景,导致任务分发严重串行化











