4. 迁移后连接池泄漏现象:应用响应变慢、数据库连接数飙升、连接池耗尽异常

数据库迁移完成后,应用层通常最先感知到异常。连接池泄漏是迁移后最典型、最隐蔽的故障模式之一,其表象与数据库性能问题高度相似,但根因往往在应用代码或连接池配置层面。本节将系统梳理连接池泄漏的三大核心现象,并建立从表象到根因的排查链路。

4.1 现象一:应用响应变慢,业务线程阻塞

用户侧最直接的感受是接口响应时间从毫秒级退化到秒级甚至超时。从应用服务器视角观察,会出现以下特征:

  • Tomcat / Jetty 线程池耗尽:活跃线程数持续接近最大值,大量请求在等待队列中排队。
  • 线程堆栈中大量线程阻塞在连接获取点:例如 HikariPool.getConnection() 或 DruidDataSource.getConnection() 调用处。
  • CPU 使用率不升反降:因为线程大部分时间在等待连接,而非执行计算。
快速定位技巧: 执行 jstack <pid> | grep -A 20 "WAITING",若大量线程处于 WAITING (parking) 状态且堆栈指向连接池内部方法,即可初步判定为连接获取阻塞。

4.2 现象二:数据库连接数飙升

通过 show processlist; 或数据库监控面板观察,活跃连接数远超预期。迁移后常见对比数据如下:

指标 迁移前(正常) 迁移后(泄漏) 异常倍数
应用实例数 10 10 —
连接池最大连接数 50 50 —
数据库总连接数 200~300 800~1200 3~4x
连接状态分布 Sleep 占 70% Sleep 占 90%+ 大量空闲连接未归还

连接数飙升的直接后果是数据库服务器内存压力增大,甚至触发 Too many connections 错误,导致新连接被拒绝。

4.3 现象三:连接池耗尽异常

当连接泄漏持续累积,连接池最终会抛出典型异常。以 HikariCP 和 Druid 为例:

// HikariCP 异常示例
Caused by: java.sql.SQLTransientConnectionException: 
  HikariPool-1 - Connection is not available, request timed out after 30000ms.

// Druid 异常示例
Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: 
  wait millis 30000, active 50, maxActive 50, creating 0

异常信息中的关键字段:

  • active / maxActive:当前活跃连接数等于最大连接数,说明所有连接都被占用且未释放。
  • request timed out:等待超时时间(默认 30 秒)耗尽,连接仍未归还。

4.4 现象关联与故障链路

三个现象并非孤立出现,而是形成一条完整的故障传导链。下图展示了从连接泄漏到业务崩溃的完整路径:

连接池泄漏故障传导链路 连接池泄漏故障传导链路 应用代码未归还连接 try-finally / try-with-resources 遗漏 连接池活跃数持续增长 active → maxActive 新请求获取连接超时 Connection is not available 数据库连接数飙升 Sleep 连接堆积 业务线程阻塞 WAITING / BLOCKED 状态 应用响应变慢 接口超时 / 雪崩 排查要点: 1. 检查应用代码中所有数据库操作是否在 finally 块中关闭连接 2. 使用连接池监控 API 获取 active / idle / pending 实时指标 3. 对比迁移前后连接获取与归还的日志时间戳

4.5 泄漏与正常行为的对比

为了帮助快速区分连接池泄漏与正常的连接波动,下表总结了关键差异:

对比维度 正常行为 泄漏行为
活跃连接数曲线 随请求量波动,波峰后回落 持续上升,不回落
空闲连接数 维持在配置的最小空闲值附近 逐渐趋近于 0
连接获取等待时间 通常 < 5ms 持续增长,直至超时
数据库 Sleep 连接 连接复用率高,Sleep 连接少 大量 Sleep 连接堆积,且长时间不消失
应用线程状态 RUNNABLE / TIMED_WAITING 交替 大量 WAITING (parking) 集中在连接池获取点

4.6 迁移场景下的特殊诱因

数据库迁移后,连接池泄漏的发生概率显著升高,主要源于以下迁移特有的变化:

  • 连接字符串变更:部分框架(如 MyBatis / Hibernate)的 DataSource 配置中,url 参数变更后,连接池内部连接验证逻辑可能失效,导致无效连接未被及时剔除。
  • 事务边界扩大:迁移后部分 SQL 执行计划变化,导致事务执行时间延长,连接持有时间超出预期,间接造成连接池耗尽。
  • 连接池配置未同步:迁移后数据库最大连接数限制降低(例如从 2000 降到 500),但应用连接池 maximumPoolSize 未同步调小,导致连接池自身先于数据库耗尽。
  • 第三方客户端库版本不兼容:例如 MySQL 8.0 驱动与旧版 HikariCP 组合,可能触发连接泄漏 bug。
迁移后必查项: 在迁移后的第一个业务高峰期,务必开启连接池的 leakDetectionThreshold(HikariCP)或 removeAbandonedTimeout(Druid),并配置连接泄漏日志输出。这能在泄漏发生的第一时间捕获未归还连接的堆栈信息,大幅缩短定位时间。

4.7 现象总结与排查优先级

当同时出现上述三个现象时,排查优先级建议如下:

  1. 第一优先级:检查应用日志中是否出现 Connection is not available 或 GetConnectionTimeoutException,确认连接池已耗尽。
  2. 第二优先级:通过 jstack 或 Arthas 查看线程堆栈,定位阻塞在连接获取点的线程数量及调用链。
  3. 第三优先级:使用连接池监控 API 获取 activeCount、idleCount、pendingCount 等实时指标,确认泄漏趋势。
  4. 第四优先级:开启连接泄漏检测,等待下一次泄漏发生时捕获未归还连接的完整堆栈。