数据库连接池
Java 中级 · 持久层框架
数据库连接的建立是昂贵的(TCP 三次握手 + 认证 + 会话初始化,通常几十毫秒)。如果每次执行 SQL 都新建连接,高并发下会瞬间打爆数据库。连接池的作用就是复用连接。本篇讲清 HikariCP 为什么快、Druid 为什么被偏爱,以及连接池参数怎么调。
一、为什么需要连接池
| 指标 | 无池 | 有池 |
|---|---|---|
| 获取连接耗时 | 几十 ms | 微秒级 |
| 数据库连接数 | 随并发爆炸 | 受池上限保护 |
| 资源 | 频繁创建销毁 | 复用 |
二、HikariCP:为什么是默认且最快
Spring Boot 2.x 起默认使用 HikariCP(日语「光」),以极简和极致性能著称。
2.1 快的三个关键设计
ConcurrentBag(无锁并发容器):借连接时优先拿本线程上次用过的连接(ThreadLocal 思想),减少竞争;无锁比LinkedBlockingQueue的锁竞争快很多。- 字节码优化:用 Javassist 生成代理、减少方法调用层级(
ProxyFactory)。 - 精简设计:代码量远小于 Druid,启动快、开销小。
2.2 核心参数
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 1maximum-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 防火墙,国内很多公司在用。
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: ****| 对比 | HikariCP | Druid |
|---|---|---|
| 性能 | 更快 | 略慢 |
| 监控 | 基础 | 强(SQL/慢查询/连接池全监控页) |
| SQL 防火墙 | 无 | 有(防注入/防批量误删) |
| 体积 | 极小 | 较大 |
| 选型 | 追求性能默认选它 | 需要监控/安全选它 |
四、连接泄漏:最常见的生产事故
「借了连接不还」,池子很快被耗尽,后续请求全部超时。
// 错误:异常路径没归还
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)。 - 多数据源:每个数据源独立池,命名区分,避免串库。
📚 官方文档与参考资料
✏️ 练习与自测
- 为什么需要连接池?不用连接池在高并发下会有什么问题?
- HikariCP 为什么快?
ConcurrentBag相比普通阻塞队列的优势是什么? - 为什么
max-lifetime必须小于数据库的wait_timeout?设大了会出现什么错误? - 写一段会产生连接泄漏的代码,再用
try-with-resources修复,并说明如何用 HikariCP 的泄漏检测排查。
本节小结
连接池的核心是复用连接、保护数据库。HikariCP 快在 ConcurrentBag 无锁 + 字节码优化,是默认首选;Druid 强在监控与 SQL 防火墙。三个必记参数:maximum-pool-size(别盲目调大)、max-lifetime(< DB wait_timeout)、connection-timeout(快速失败)。连接泄漏是头号事故,一律 try-with-resources。