锁等待超时是事务互相阻塞而非数据库卡顿,需优先定位长事务、其持有锁及等待者;连接池满常因慢事务“焊死”连接,非连接数不足。

锁等待超时不是数据库“卡了”,是事务在互相堵车
看到 Lock wait timeout exceeded; try restarting transaction,第一反应不该是“调大 innodb_lock_wait_timeout”,而是查:哪个事务卡在那儿没走完?它锁了什么?谁在等它?连接池里堆积的其实是“等锁的线程”,不是空闲连接——这些线程占着连接不释放,又挡着新请求进来,形成恶性循环。
连接池满 ≠ 连接数不够,很可能是慢事务把连接“焊死”了
常见误判:监控显示连接池使用率 100%,就立刻扩容连接数或重启应用。但真实情况往往是——几个长事务(比如耗时 80 秒的订单同步)一直没提交,它们持有的连接被标记为“活跃”,却不再执行 SQL;后续所有想操作同一张表的请求全被堵在锁队列里,连接池越配越大,问题越拖越重。
- 检查
SHOW PROCESSLIST,重点关注State列为Updating、Locked或长时间Sleep但Command是Query的线程 - 用
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), TRX_STARTED)) > 30找出运行超 30 秒的事务 - 确认应用端是否开了隐式事务(如 Spring 的
@Transactional方法没正常返回/抛异常未回滚),或 ORM 自动开启了事务但忘记 commit
慢事务背后常藏三个硬伤:索引缺失、自增锁模式、间隙锁滥用
一个 UPDATE user SET status = 1 WHERE create_time 跑 60 秒还锁表?大概率不是数据量大,而是没走索引——全表扫描意味着锁住几十万行,其他事务一碰这张表就排队。更隐蔽的是 RR 隔离级别下,哪怕只更新一行,也可能因间隙锁把相邻范围也锁住,导致并发插入/更新直接撞墙。
- 用
EXPLAIN看执行计划,确认type不是ALL,key列有实际使用的索引 - 批量插入场景下,若
innodb_autoinc_lock_mode = 1(默认),整个 INSERT … SELECT 会持锁到语句结束;改成2可降级为轻量级锁,但需 MySQL 5.6+ 且 binlog_format=ROW - 非必要别用
SELECT ... FOR UPDATE包裹整页列表查询;真要加锁,先用带索引条件的主键查询定位,再锁单行
别让连接池参数掩盖真实问题
maxActive(Druid)或 maximumPoolSize(HikariCP)调太高,只会把慢事务的破坏力放大——更多线程同时冲向同一张没索引的表,锁冲突指数级上升。而 minIdle 设太大,空闲连接长期占用,反而挤占系统资源。
- 连接池
validationQuery别用SELECT 1,改用SELECT 1 FROM DUAL(MySQL 兼容性更好),避免某些驱动在检测时触发隐式事务 - 开启连接泄漏检测(如 Druid 的
removeAbandonedOnBorrow=true),但注意:它只能回收“疑似泄漏”的连接,不能替代事务逻辑修复 - 真正有效的兜底是应用层超时:
@Transactional(timeout = 5)或 MyBatis 的timeout属性,强制事务在 5 秒内必须结束,否则回滚释放锁
最常被忽略的一点:很多“锁等待超时”报错出现在凌晨定时任务跑批时,而开发白天压根收不到告警。得把慢事务监控和锁等待日志(innodb_status_output + 慢日志中的 Lock_time)连起来看,否则永远在修表面,堵不住源头。











