数据库连接池耗尽排查
【摘要】 以 Spring Boot 3.x 默认的 HikariCP 为例;Druid 现象与思路一致。一、典型现象日志刷屏:HikariPool-1 - Connection is not available, request timed out after 30000ms。接口大面积超时,但应用 CPU、DB CPU 都不高(因为线程都在等连接)。监控上看 hikaricp_connection...
以 Spring Boot 3.x 默认的 HikariCP 为例;Druid 现象与思路一致。
一、典型现象
- 日志刷屏:
HikariPool-1 - Connection is not available, request timed out after 30000ms。 - 接口大面积超时,但应用 CPU、DB CPU 都不高(因为线程都在等连接)。
- 监控上看
hikaricp_connections_active等于maximumPoolSize,hikaricp_connections_pending持续 > 0。 - 重启后立刻恢复,几分钟到几十分钟后复发(典型泄漏/慢 SQL 特征)。
二、快速定位
1)看池指标(最直接)
yaml
# application.yml 打开指标
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
关注四个数:active(借出)、idle(空闲)、pending(等连接的线程数)、以及 threadsAwaitingConnection。
active长期顶格 +pending> 0 → 池太小 或 连接被长时间占用。idle为 0 但active不满 → 有连接"失踪",即泄漏。
2)抓线程栈,看线程卡在哪(最关键的一步)
bash
jstack <pid> > /tmp/jstack_$(date +%H%M%S).txt
在栈里搜索 HikariPool / getConnection:如果几十个线程都在 HikariPool.getConnection → 确认是池耗尽。 如果线程都卡在 SocketInputStream.read / HttpClient → 说明是事务里做远程调用。
3)确认是被慢 SQL 占用还是泄漏
sql
-- 看 DB 侧真实连接数与状态
SHOW PROCESSLIST;
SELECT COUNT(*) FROM information_schema.processlist WHERE db = 'your_db' AND command <> 'Sleep';
DB 侧连接数 = 池上限且都是 Sleep(事务未提交)→ 应用拿到了连接不还。
4)打开泄漏探测
yaml
spring:
datasource:
hikari:
leak-detection-threshold: 20000 # 连接借出超过 20s 未归还就打堆栈
日志会直接打出"哪个方法借了连接没还"的调用栈,定位效率最高。
三、常见根因清单
- 慢 SQL 占满池(最高频):根因在 SQL,池只是受害者 → 先看 01_慢SQL与索引失效排查.md。
- 事务内做非 DB 耗时操作:
@Transactional方法里调 RPC、HTTP、文件、发 MQ、加分布式锁,导致连接被持有几百毫秒到几秒。 - 连接泄漏:手动
dataSource.getConnection()后异常路径没close();JdbcTemplate之外的裸 JDBC 代码是重灾区。 - 池配置与并发不匹配:QPS 高 + 单次 SQL 慢,需要的连接数 = QPS × 单次耗时(秒)。
maxLifetime与 DBwait_timeout/ 中间件空闲断连不匹配:拿到的连接已被服务端关闭,首包失败后重试,表现像"偶发超时"。- 长事务:手动开事务不提交、
@Transactional包了整段批处理。 connection-timeout太短/太长:太短导致正常排队也报错;太长导致故障时线程全挂住,雪崩更慢。
四、处理步骤
1)给池做合理配置(示例,按实际压测调)
yaml
spring:
datasource:
hikari:
maximum-pool-size: 20 # 单实例上限,DB 总连接数 = 实例数 × 本值,别超过 DB max_connections
minimum-idle: 5 # 与 maximum-pool-size 接近可减少扩缩容抖动
connection-timeout: 3000 # 拿连接超时,故障时快速失败,别用默认 30s
validation-timeout: 3000
idle-timeout: 600000 # 10min
max-lifetime: 1740000 # 29min,必须 < DB wait_timeout,通常留 30~60s 余量
leak-detection-threshold: 20000
HikariCP 官方经验值:
connections = ((CPU核心数 * 2) + 有效磁盘数),不要盲目调大。 池调大只缓解症状,总连接数超过 DB 承载会让所有连接一起变慢。
2)把远程调用挪出事务
java
// 差:整个方法在事务里,RPC 期间一直占着连接
@Transactional
public void createOrder(Req req) {
orderMapper.insert(req.toOrder());
remoteClient.notify(req); // 几百 ms,连接一直被占
}
// 好:事务只包 DB 写,RPC 放到事务外
public void createOrder(Req req) {
insertInTx(req);
remoteClient.notify(req);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertInTx(Req req) { orderMapper.insert(req.toOrder()); }
3)修泄漏:所有裸 JDBC 一律 try-with-resources。
java
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
// ...
}
4)紧急止血:临时把 maximum-pool-size 调大(有限度)+ 扩容实例;同时对超时接口做限流降级,避免请求继续堆积。
五、验证方法
hikaricp_connections_pending归零、threadsAwaitingConnection归零,Connection is not available日志消失。- 压测:并发打到峰值,
active有回落波动(能借能还),而不是一条直线顶格。 - 泄漏探测日志不再出现
Apparent connection leak detected。 - DB 侧
SHOW PROCESSLIST连接数与池上限一致,无常驻Sleep长事务。
六、事后预防
- 项目规范:
@Transactional方法内禁止远程调用/IO/加锁等待,可加到 code review checklist 甚至静态检查。 - 池指标接入告警:
pending > 0持续 1 分钟、active/max > 0.9持续 5 分钟即告警。 max-lifetime< DBwait_timeout作为强制配置项,写进团队脚手架默认值。- 用
connection-timeout ≤ 3s+ 熔断,避免"慢故障"拖垮整个应用。
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)