前言

数据库连接池耗尽经常表现为接口超时,但根因可能不在连接池本身。它可能由慢 SQL、大事务、连接泄漏、线程池堆积、数据库锁等待或下游变慢引起。

典型现象

  • 日志出现 Connection is not availableTimeout waiting for idle object
  • HikariCP active 连接接近 maximumPoolSize。
  • 接口大量超时。
  • 数据库 QPS 下降但连接数很高。
  • Tomcat 线程阻塞等待数据库连接。

故障链路

flowchart TD
    A[慢SQL或慢事务] --> B[连接占用时间变长]
    B --> C[连接池active打满]
    C --> D[请求线程等待连接]
    D --> E[Tomcat线程堆积]
    E --> F[接口整体变慢]
    F --> G[重试增加]
    G --> C

排查步骤

看连接池指标

重点指标:

  • active 连接数。
  • idle 连接数。
  • pending 等待线程数。
  • connection timeout 次数。
  • 连接获取耗时。

如果 active 高、pending 高,说明连接被长期占用。下一步要看谁占用了连接。

看数据库会话

MySQL 常用:

1
2
SHOW FULL PROCESSLIST;
SHOW ENGINE INNODB STATUS;

重点看:

  • 长时间 running 的 SQL。
  • Sleep 时间异常长的连接。
  • 等锁的事务。
  • 是否有批量更新或大查询。

看慢事务

连接不释放常见于事务时间太长:

  • 事务里调用第三方接口。
  • 事务里处理大批量数据。
  • 事务里执行复杂查询。
  • 事务开启后做大量非数据库逻辑。
  • 异常分支未正确结束。

事务应该只包住必须保证一致性的数据库操作,不应该包住远程调用、文件处理和复杂计算。

看连接泄漏

如果使用 MyBatis、Spring JdbcTemplate、JPA,一般连接会由框架释放。但以下情况仍可能泄漏:

  • 手写 JDBC 未关闭连接。
  • 自己管理事务。
  • 多数据源切换异常。
  • 流式查询 ResultSet 未关闭。
  • 异步线程中使用事务上下文不当。

HikariCP 可配置 leakDetectionThreshold 辅助发现连接泄漏,但线上要合理设置,避免误报。

常见处理方案

问题 临时处理 长期治理
慢 SQL 加限流、杀异常 SQL 索引优化、SQL 改写
慢事务 暂停任务、回滚版本 缩小事务范围
连接泄漏 重启实例 修复连接关闭逻辑
连接池太小 临时调大 按容量压测配置
DB 锁等待 找阻塞事务 优化更新顺序和索引

参数配置思路

连接池不是越大越好。连接太多会增加数据库上下文切换和锁竞争。

配置时考虑:

  • 应用实例数。
  • 每实例最大并发。
  • 数据库最大连接数。
  • SQL 平均耗时和 P99。
  • 是否有读写分离。
  • 是否存在批处理任务。

例如数据库最大连接数 1000,应用 10 个实例,不能每个实例都配置 200 个连接。还要给运维、迁移、监控和其他服务留余量。

生产止血

  • 对慢接口限流。
  • 暂停批处理和报表任务。
  • 杀掉异常长事务。
  • 临时扩容只读实例或应用实例。
  • 降低重试次数。
  • 回滚最近发布。

如果连接池已经耗尽,不要盲目重试。重试会增加排队请求,进一步拖垮系统。

高频面试题

连接池耗尽怎么排查?

先看连接池 active、idle、pending 和获取连接耗时,再看数据库 processlist、慢 SQL、锁等待和长事务,最后结合线程栈判断请求是否大量阻塞在获取连接。

连接池调大能解决问题吗?

不一定。如果根因是慢 SQL 或连接泄漏,调大只会延迟故障并增加数据库压力。应该先找到连接被谁占用。

事务为什么不能包远程调用?

远程调用不可控,可能超时或重试,会导致数据库连接和锁长时间占用,放大连接池耗尽和锁等待风险。

总结

连接池耗尽是典型连锁故障。面试回答要讲清楚连接池、线程池、数据库锁、慢 SQL 和事务范围之间的关系。