Skip to content

数据库连接池 ​

Java 中级 · 持久层框架

数据库连接的建立是昂贵的(TCP 三次握手 + 认证 + 会话初始化,通常几十毫秒)。如果每次执行 SQL 都新建连接,高并发下会瞬间打爆数据库。连接池的作用就是复用连接。本篇讲清 HikariCP 为什么快、Druid 为什么被偏爱,以及连接池参数怎么调。

一、为什么需要连接池 ​

指标无池有池
获取连接耗时几十 ms微秒级
数据库连接数随并发爆炸受池上限保护
资源频繁创建销毁复用

二、HikariCP:为什么是默认且最快 ​

Spring Boot 2.x 起默认使用 HikariCP(日语「光」),以极简和极致性能著称。

2.1 快的三个关键设计 ​

  1. ConcurrentBag(无锁并发容器):借连接时优先拿本线程上次用过的连接(ThreadLocal 思想),减少竞争;无锁比 LinkedBlockingQueue 的锁竞争快很多。
  2. 字节码优化:用 Javassist 生成代理、减少方法调用层级(ProxyFactory)。
  3. 精简设计:代码量远小于 Druid,启动快、开销小。

2.2 核心参数 ​

yaml
spring:
  datasource:
    hikari:
      maximum-pool-size: 10          # 最大连接数(不是越大越好!)
      minimum-idle: 10               # 最小空闲连接(与 max 相同可避免抖动)
      connection-timeout: 30000      # 获取连接超时(ms),默认30s
      idle-timeout: 600000           # 空闲超时(ms)
      max-lifetime: 1800000          # 连接最大存活(ms),默认30min,需 < DB wait_timeout
      pool-name: OrderHikariPool
      connection-test-query: SELECT 1

maximum-pool-size 不是越大越好

连接数过多会导致:线程上下文切换增加、数据库侧连接管理开销变大、锁竞争加剧。经典公式(HikariCP 官方推荐): 连接数 = CPU 核数 × 2 + 磁盘数。很多场景 10 就够,盲目调大反而更慢。

max-lifetime 必须小于数据库的 wait_timeout

MySQL 默认 wait_timeout = 28800s(8h)。若 maxLifetime 比它大,连接会被 MySQL 单方面掐断,客户端拿到的是失效连接,报 Communications link failure。生产一般设 max-lifetime 为 30 分钟。

三、Druid:监控与安全 ​

Druid 是阿里开源,最大亮点是内置强大的监控和SQL 防火墙,国内很多公司在用。

yaml
spring:
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/db
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000              # 获取连接最大等待
      # 监控过滤器
      filters: stat,wall,slf4j
      stat-view-servlet:           # 内置监控页面 /druid
        enabled: true
        login-username: admin
        login-password: ****
对比HikariCPDruid
性能更快略慢
监控基础强(SQL/慢查询/连接池全监控页)
SQL 防火墙无有(防注入/防批量误删)
体积极小较大
选型追求性能默认选它需要监控/安全选它

四、连接泄漏:最常见的生产事故 ​

「借了连接不还」,池子很快被耗尽,后续请求全部超时。

java
// 错误:异常路径没归还
Connection conn = dataSource.getConnection();
Statement st = conn.prepareStatement(sql);   // 若此处抛异常,conn 永远不归还
// ... 
conn.close();

// 正确:try-with-resources 自动关闭
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setLong(1, id);
    try (ResultSet rs = ps.executeQuery()) { /* ... */ }
}   // 块结束自动 close,归还连接池

泄漏排查

  • 现象:日志频繁 Connection is not available, request timed out。
  • 排查:Druid 监控页看活跃连接;HikariCP 可开 leak-detection-threshold: 2000(借出 2s 未还就打栈)。
  • 根因:手写 JDBC 忘记 close、MyBatis 手动 SqlSession 未关、异常分支漏关。

五、最佳实践 ​

  • 永远用 try-with-resources 或框架托管,绝不裸奔 close()。
  • max-lifetime < 数据库 wait_timeout,避免拿到失效连接。
  • minimum-idle = maximum-pool-size(HikariCP 建议)避免池子反复伸缩。
  • connection-timeout 设短些(如 3s),快速失败优于长时间挂起。
  • 开启慢 SQL 监控(Druid filters: stat 或 HikariCP + Micrometer)。
  • 多数据源:每个数据源独立池,命名区分,避免串库。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 为什么需要连接池?不用连接池在高并发下会有什么问题?
  2. HikariCP 为什么快?ConcurrentBag 相比普通阻塞队列的优势是什么?
  3. 为什么 max-lifetime 必须小于数据库的 wait_timeout?设大了会出现什么错误?
  4. 写一段会产生连接泄漏的代码,再用 try-with-resources 修复,并说明如何用 HikariCP 的泄漏检测排查。

本节小结

连接池的核心是复用连接、保护数据库。HikariCP 快在 ConcurrentBag 无锁 + 字节码优化,是默认首选;Druid 强在监控与 SQL 防火墙。三个必记参数:maximum-pool-size(别盲目调大)、max-lifetime(< DB wait_timeout)、connection-timeout(快速失败)。连接泄漏是头号事故,一律 try-with-resources。