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 现象关联与故障链路
三个现象并非孤立出现,而是形成一条完整的故障传导链。下图展示了从连接泄漏到业务崩溃的完整路径:
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 现象总结与排查优先级
当同时出现上述三个现象时,排查优先级建议如下:
- 第一优先级:检查应用日志中是否出现
Connection is not available或GetConnectionTimeoutException,确认连接池已耗尽。 - 第二优先级:通过
jstack或 Arthas 查看线程堆栈,定位阻塞在连接获取点的线程数量及调用链。 - 第三优先级:使用连接池监控 API 获取
activeCount、idleCount、pendingCount等实时指标,确认泄漏趋势。 - 第四优先级:开启连接泄漏检测,等待下一次泄漏发生时捕获未归还连接的完整堆栈。