线程池耗尽本质是连接被长期占用或泄漏,需重点排查SHOW PROCESSLIST中Sleep超300秒、Locked/Waiting for table metadata lock、Connect且Time≈0三类异常连接,并优化事务设计、索引及连接池配对参数。

线程池耗尽不是锁问题,而是连接资源被长期占用或泄漏的表象;真正要防的,是事务不释放连接、锁不释放、以及应用层无节制建连。
查清连接到底卡在哪类状态
别只盯着 Threads_connected 数值,重点看 SHOW PROCESSLIST 里三类连接:
-
State = 'Sleep'且Time > 300:大概率是应用获取连接后没 close,或连接池空闲回收时间设得太长 -
State = 'Locked'或'Waiting for table metadata lock':背后常是未提交事务、慢查询、或 DDL 正在执行 -
Command = 'Connect'且Time ≈ 0:上游在疯狂重试建连,说明已有大量请求在等连接,不是 DB 慢,是连接池崩了
事务里别混非数据库操作
锁等待时间 = 事务开启到提交之间的全部耗时。哪怕只更新一行,如果事务里夹着 HTTP 调用、日志写入、循环计算,那这行锁就一直挂着,连接也一直占着不放。
- 把
SELECT FOR UPDATE后的业务逻辑全移出事务块:先查 ID 列表 → 算逻辑 → 最后UPDATE ... WHERE id IN (...)单条提交 - 发消息、调第三方 API、生成文件这些操作,一律改用异步队列或最终一致性补偿
- 批量更新必须分片:每 100~500 行一个事务,用
COMMIT主动释放锁和连接,而不是包 10 万行在一个事务里硬扛
连接池参数必须配对生效
光设 maximumPoolSize(或 maxActive)没用,关键参数缺一不可:
-
connection-timeout(HikariCP)或maxWait(Druid)必须设,建议 3000ms;否则线程无限等连接,直接拖垮服务 -
idle-timeout(HikariCP)或minEvictableIdleTimeMillis(Druid)建议设为 60000(60 秒),防止“僵尸连接”长期挂池中 -
leak-detection-threshold(HikariCP)或removeAbandonedOnBorrow(Druid)务必打开,主动回收疑似泄漏的连接
注意:idle-timeout 设成 30 分钟,等于默许连接池留一堆 DB 早已踢掉的失效连接,最终导致连接数虚高、真实可用连接越来越少。
索引缺失会让锁问题雪上加霜
没有索引的 UPDATE 会触发全表扫描,InnoDB 行锁退化为接近表级锁,所有并发更新都排队等同一个锁——这不仅放大死锁概率,更让连接在锁等待队列里挂满 50 秒(innodb_lock_wait_timeout 默认值),等于每个失败请求都白占一个连接近一分钟。
- 用
EXPLAIN SELECT * FROM t WHERE xxx验证 WHERE 条件是否走索引;type是ALL或index就是扫全表 - 高频更新字段必须建索引;组合条件优先建联合索引,顺序按
WHERE中出现顺序来,比如WHERE status = ? AND created_at > ?就建(status, created_at) - 避免隐式类型转换:如
user_id是BIGINT,但传字符串'123',索引直接失效
最常被忽略的一点:innodb_lock_wait_timeout 不是救命稻草。设太长(比如 120 秒),用户得等两分钟才看到错误,体验比立刻失败还差;设太短(比如 5 秒),又容易误杀正常慢事务。权衡后建议设为 10~20 秒,并配合应用层重试策略。


















