数据库连接池耗尽排查

举报
yd_283123001 发表于 2026/11/13 09:20:53 2026/11/13
【摘要】 以 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 未归还就打堆栈

日志会直接打出"哪个方法借了连接没还"的调用栈,定位效率最高。

三、常见根因清单

  1. 慢 SQL 占满池(最高频):根因在 SQL,池只是受害者 → 先看 01_慢SQL与索引失效排查.md。
  2. 事务内做非 DB 耗时操作:@Transactional 方法里调 RPC、HTTP、文件、发 MQ、加分布式锁,导致连接被持有几百毫秒到几秒。
  3. 连接泄漏:手动 dataSource.getConnection() 后异常路径没 close();JdbcTemplate 之外的裸 JDBC 代码是重灾区。
  4. 池配置与并发不匹配:QPS 高 + 单次 SQL 慢,需要的连接数 = QPS × 单次耗时(秒)。
  5. maxLifetime 与 DB wait_timeout / 中间件空闲断连不匹配:拿到的连接已被服务端关闭,首包失败后重试,表现像"偶发超时"。
  6. 长事务:手动开事务不提交、@Transactional 包了整段批处理。
  7. 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 < DB wait_timeout 作为强制配置项,写进团队脚手架默认值。
  • 用 connection-timeout ≤ 3s + 熔断,避免"慢故障"拖垮整个应用。
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。