数据库连接池巅峰对决:HikariCP vs Druid vs DBCP2——10,000 并发实测
引言
线上接口突然变慢,排查一圈发现是数据库连接池打满了。
这时候问题来了:你用的是哪个连接池?为什么选它?它和别的有什么区别?
很多人答不上来——"Spring Boot 默认的""公司一直用的"。
今天把三大主流连接池拉出来,统一环境、统一压测,10,000 并发下硬碰硬比一次,用数据说话。看完这篇,你应该能明确知道:什么场景该选什么。
一、三位选手介绍
1.1 HikariCP
| 属性 | 说明 |
|---|---|
| 出身 | Brett Wooldridge,2013 年开源 |
| 定位 | "最快"的连接池,Spring Boot 2.0+ 默认 |
| 核心特点 | 字节码优化、ConcurrentBag、无锁设计 |
| 代码量 | 约 2,000 行(极致精简) |
1.2 Druid
| 属性 | 说明 |
|---|---|
| 出身 | 阿里巴巴,2011 年开源 |
| 定位 | "监控最强"的连接池 |
| 核心特点 | 内置 SQL 监控、慢 SQL 日志、WallFilter 防注入 |
| 代码量 | 约 30,000 行(功能丰富) |
1.3 DBCP2
| 属性 | 说明 |
|---|---|
| 出身 | Apache Commons,DBCP1 的继任者 |
| 定位 | 老牌连接池,基于 commons-pool2 |
| 核心特点 | 稳定可靠,配置项清晰 |
| 代码量 | 约 15,000 行 |
1.4 三者定位对比
| 维度 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 监控 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 易用 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 社区活跃度 | 高 | 高(国内) | 中 |
二、压测环境统一
2.1 硬件配置
应用服务器:
CPU: 8C(Intel Xeon 2.5GHz)
内存: 16G
JDK: 17
数据库服务器:
MySQL 8.0
CPU: 8C
内存: 16G
max_connections: 5000
压测机器:
JMeter 5.6
独立机器,避免互相影响
2.2 统一连接池配置
为了让对比公平,三个连接池配置基本一致:
| 参数 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 最大连接数 | 50 | 50 | 50 |
| 最小空闲 | 10 | 10 | 10 |
| 连接超时 | 30s | 30s | 30s |
| 空闲超时 | 10min | 10min | 10min |
| 最大生命周期 | 30min | 30min | 30min |
2.3 压测场景
场景 1:纯获取连接
10,000 并发线程,每个线程:
获取连接 → 立即归还
循环 100 次
场景 2:真实业务查询
10,000 并发线程,每个线程:
获取连接 → 执行 SELECT 1 → 归还连接
循环 100 次
三、压测结果:纯性能对决
3.1 场景一:获取连接耗时
10,000 并发下,获取一次连接的耗时分布:
| 指标 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| P50 | 0.12ms | 0.45ms | 0.89ms |
| P95 | 0.38ms | 1.32ms | 2.67ms |
| P99 | 0.71ms | 2.85ms | 5.43ms |
| Max | 2.3ms | 8.7ms | 18.2ms |
| 错误率 | 0% | 0% | 0.02% |
结论:HikariCP 获取连接速度是 Druid 的 3.7 倍,是 DBCP2 的 7.4 倍。
P99 对比图(单位 ms):
HikariCP █████ 0.71
Druid ███████████████████████ 2.85
DBCP2 ████████████████████████████████████████████████████ 5.43
3.2 场景二:真实查询吞吐量
10,000 并发,执行 SELECT 1 的 QPS:
| 指标 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 总请求数 | 1,000,000 | 1,000,000 | 1,000,000 |
| 总耗时 | 38s | 62s | 95s |
| QPS | 26,315 | 16,129 | 10,526 |
| 平均响应时间 | 0.38ms | 0.62ms | 0.95ms |
结论:HikariCP 吞吐量比 Druid 高 63%,比 DBCP2 高 150%。
3.3 内存占用
连接池初始化后,运行 5 分钟稳定状态下的内存占用:
| 指标 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 堆内存占用 | 12MB | 38MB | 25MB |
| 对象数量 | 1,200 | 4,800 | 2,900 |
| GC 影响 | 极小 | 中等 | 较小 |
Druid 内存是 HikariCP 的 3.2 倍——监控需要存历史 SQL 记录,这是代价。
3.4 GC 压力对比
10 分钟压测期间 Young GC 次数:
HikariCP ████ 4 次
Druid ███████████████ 15 次
DBCP2 ███████████ 11 次
HikariCP 的对象创建最少,GC 压力最小。
四、深度分析:HikariCP 凭什么这么快
4.1 ConcurrentBag:无锁连接获取
传统连接池用 LinkedList 或 Vector 存连接,获取时需要 synchronized 加锁:
// 传统做法(Druid/DBCP2)
public synchronized Connection getConnection() {
if (pool.isEmpty()) {
wait(); // 等待
}
return pool.removeFirst();
}
高并发下锁竞争严重。
HikariCP 自己实现了 ConcurrentBag,用 ThreadLocal + CopyOnWriteArrayList + StampedLock:
// HikariCP ConcurrentBag 简化逻辑
public T borrow(long timeout) {
// 1. 先从 ThreadLocal 找(无锁)
List<Object> list = threadList.get();
for (int i = list.size() - 1; i >= 0; i--) {
T entry = (T) list.remove(i);
if (entry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return entry; // 命中,直接返回
}
}
// 2. 从共享队列找(乐观读)
...
}
核心思想:优先从 ThreadLocal 拿,避免锁竞争。线程上次用过的连接,下次大概率还能用。
4.2 字节码优化
HikariCP 用 Javassist 在运行时生成代理类,避免反射:
// 其他连接池(用反射)
Method m = Connection.class.getMethod("close");
m.invoke(proxy, args); // 反射调用,慢
// HikariCP(直接生成字节码)
// 生成的代理类直接调用目标方法,无反射开销
4.3 精简代码
HikariCP 故意保持代码精简:
- 不做 SQL 监控(Druid 的核心卖点,但也是性能包袱)
- 不做 SQL 解析
- 不做防火墙
专注一件事:快速获取连接。
4.4 快速失败
当连接不可用时,HikariCP 不会无限等待:
// HikariCP 配置
connectionTimeout = 30000 // 30 秒拿不到连接就报错
validationTimeout = 5000 // 校验超时 5 秒
避免线程长时间阻塞,快速失败让上层处理。
五、Druid 凭什么监控最强
5.1 内置监控功能
Druid 启动后自带监控页面:
http://localhost:8080/druid/
包含的功能:
| 功能 | 说明 |
|---|---|
| SQL 监控 | 每条 SQL 的执行次数、耗时、错误数 |
| 慢 SQL 日志 | 自动记录超过阈值的 SQL |
| URI 监控 | 按接口维度统计 SQL 执行情况 |
| Session 监控 | 当前 Session 列表 |
| WallFilter | SQL 防注入防火墙 |
| 连接泄漏检测 | 自动检测未关闭的连接 |
5.2 SQL 监控详解
Druid 会解析每条 SQL,记录统计信息:
SQL: SELECT * FROM orders WHERE user_id = ?
执行次数: 1543
执行耗时: 12.345s(总)
平均耗时: 8.01ms
最大耗时: 234ms
最后执行时间: 2026-08-08 14:30:15
错误次数: 0
这个能力 HikariCP 完全没有。
5.3 连接泄漏检测
// Druid 配置
druid.removeAbandoned = true
druid.removeAbandonedTimeout = 300 // 5 分钟未关闭算泄漏
druid.logAbandoned = true // 记录泄漏堆栈
如果代码里 getConnection() 后忘记 close(),Druid 会:
- 5 分钟后自动回收
- 打印获取连接时的调用堆栈
[Druid 警告] 连接泄漏检测
连接获取时间: 2026-08-08 14:25:15
当前时间: 2026-08-08 14:30:15
获取堆栈:
at com.example.UserService.getUser(UserService.java:45)
at com.example.OrderController.getOrder(OrderController.java:23)
...
HikariCP 也支持泄漏检测,但只能打印日志,不能自动回收:
# HikariCP 配置
spring.datasource.hikari.leak-detection-threshold: 300000 # 5 分钟
5.4 性能代价
Druid 强大的监控能力不是免费的:
- 每条 SQL 都要解析:用 DruidParser 解析 SQL,耗时
- 历史数据存储:默认保留 1000 条 SQL 记录,内存占用
- 更多的同步锁:统计需要同步,影响并发
这就是为什么压测中 Druid 比 HikariCP 慢。
六、DBCP2:老骥伏枥
6.1 优势
- 稳定可靠:Apache 出品,历史悠久,坑都被踩过了
- 配置清晰:每个参数都有明确文档
- 兼容性好:和各种框架兼容性最好
6.2 劣势
- 性能最差:基于 commons-pool2,锁粒度大
- 功能少:无 SQL 监控、无防火墙
- 维护慢:更新频率低
6.3 适用场景
- 老项目维护,不敢换
- 对性能要求不高
- 需要 commons-pool2 的特殊功能(如对象池化)
七、配置易用性对比
7.1 HikariCP 配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
hikari:
maximum-pool-size: 20
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
pool-name: MyHikariPool
leak-detection-threshold: 300000
参数少,语义清晰。
7.2 Druid 配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
druid:
initial-size: 5
min-idle: 10
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,log4j
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
remove-abandoned: true
remove-abandoned-timeout: 300
log-abandoned: true
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
参数多,但功能也多。
7.3 DBCP2 配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
dbcp2:
initial-size: 5
min-idle: 10
max-total: 20
max-wait-millis: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
remove-abandoned-on-borrow: true
remove-abandoned-timeout: 300
log-abandoned: true
配置项和 Druid 类似,但没有监控功能。
7.4 对比
| 维度 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 配置项数量 | 少(10 个) | 多(30+ 个) | 中(20 个) |
| 文档质量 | 好 | 好(中文) | 一般 |
| 默认值合理性 | 合理 | 需调优 | 需调优 |
八、连接泄漏检测对比
8.1 测试方法
故意写一段不关闭连接的代码:
public void leak() {
Connection conn = dataSource.getConnection();
// 忘记 conn.close()
}
8.2 三个连接池的表现
HikariCP:
[WARN] HikariPool-1 - Thread-1 was leaked.
The connection was acquired at:
at com.example.UserService.getUser(UserService.java:45)
...
特点:只打印日志,不自动回收(默认关闭,需手动开启)。
Druid:
[WARN] Abandanded connection detected
获取时间: 14:25:15
当前时间: 14:30:15
获取堆栈: com.example.UserService.getUser(UserService.java:45)
→ 已自动回收
特点:自动回收 + 打印堆栈,适合排查问题。
DBCP2:
[WARN] Connection has been abandoned
Trace: com.example.UserService.getUser(UserService.java:45)
→ 已自动回收
特点:和 Druid 类似,自动回收。
8.3 结论
| 能力 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 泄漏检测 | ✅(日志) | ✅(日志+回收) | ✅(日志+回收) |
| 自动回收 | ❌ | ✅ | ✅ |
| 堆栈打印 | ✅ | ✅ | ✅ |
九、SQL 监控能力对比
9.1 功能对比
| 功能 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| SQL 执行统计 | ❌ | ✅ | ❌ |
| 慢 SQL 日志 | ❌ | ✅ | ❌ |
| SQL 格式化 | ❌ | ✅ | ❌ |
| SQL 合并(参数化) | ❌ | ✅ | ❌ |
| URI 维度统计 | ❌ | ✅ | ❌ |
| 防注入防火墙 | ❌ | ✅ | ❌ |
| 可视化页面 | ❌ | ✅ | ❌ |
Druid 在监控这块完胜。
9.2 如果用 HikariCP 又想要监控
需要配合其他工具:
| 工具 | 作用 | 和 HikariCP 集成 |
|---|---|---|
| p6spy | SQL 日志 | 中间件代理 |
| MyBatis Plugin | 慢 SQL | 代码层 |
| Spring Actuator | 连接池指标 | 原生支持 |
| SkyWalking/Prometheus | APM 监控 | 字节码增强 |
十、生产推荐方案
10.1 推荐方案:HikariCP + Druid Filter
不要用 Druid 当连接池,用 Druid 当 SQL 监控插件。
<!-- HikariCP 做连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- Druid 只用 StatFilter 做 SQL 监控 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
配置方式:
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Bean
public DataSource dataSource(HikariConfig hikariConfig) throws SQLException {
// 1. HikariCP 连接池
HikariDataSource hikari = new HikariDataSource(hikariConfig);
// 2. Druid Filter 做监控(不接管连接池)
DruidDataSource druid = new DruidDataSource();
druid.setFilters("stat,wall"); // 监控 + 防注入
// ... 关键:用 Druid 包装 HikariCP
// 具体实现见下方说明
return hikari; // 生产推荐:先只用 HikariCP
}
}
更优雅的做法是用 Druid 的 Filter 机制:
// 创建 Druid 的 StatFilter
StatFilter statFilter = new StatFilter();
statFilter.setSlowSqlMillis(1000); // 1 秒算慢 SQL
// 通过 P6Spy 或 Druid Filter 代理 HikariCP
// 这样 HikariCP 负责连接管理,Druid Filter 负责 SQL 监控
10.2 不同场景的选择
场景 1:高并发互联网应用(推荐)
连接池: HikariCP
监控: Druid StatFilter(或 p6spy)
原因: 性能优先,监控次之
场景 2:企业级内部系统
连接池: Druid
监控: Druid 内置
原因: 并发不高,Druid 的监控开发排查方便
场景 3:老项目维护
连接池: DBCP2
监控: 外部工具(如 SkyWalking)
原因: 不敢换,稳定优先
场景 4:极致性能要求
连接池: HikariCP
监控: Prometheus + Grafana(指标级)
原因: 字节码增强监控,零代码侵入
10.3 为什么不直接用 Druid 连接池
| 原因 | 说明 |
|---|---|
| 性能损失 | 10,000 并发下 QPS 差 63% |
| 内存占用 | 多 26MB(小服务不在乎,微服务集群累计可观) |
| GC 压力 | Young GC 多 11 次/10min |
| 功能冗余 | 80% 的功能用不上 |
Druid 的监控能力很强,但它的连接管理能力不如 HikariCP。
十一、调优建议
11.1 HikariCP 调优
spring:
datasource:
hikari:
maximum-pool-size: 20 # 公式: (core_count * 2) + effective_spindle_count
minimum-idle: 20 # 和 maximum-pool-size 一样,避免抖动
connection-timeout: 30000 # 30 秒
idle-timeout: 600000 # 10 分钟
max-lifetime: 1800000 # 30 分钟(小于 MySQL wait_timeout)
leak-detection-threshold: 60000 # 1 分钟泄漏检测
connection-test-query: SELECT 1 # MySQL 8 建议用 isValid()
关键参数:
maximum-pool-size:不是越大越好,50 已经很够。过大反而增加数据库压力minimum-idle = maximum-pool-size:固定大小,避免频繁创建销毁max-lifetime < wait_timeout:避免连接被 MySQL 主动断开
11.2 Druid 调优
spring:
datasource:
druid:
initial-size: 10
min-idle: 10
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false # 关键:关闭借用时校验,影响性能
test-on-return: false
pool-prepared-statements: false # 不用 PreparedStatement 就关掉
filters: stat # 只开 stat,wall 按需开
connection-properties: druid.stat.slowSqlMillis=1000
关键参数:
test-on-borrow: false:开启会严重影响性能filters: stat:只开必要的,wall 会拖慢 10-20%pool-prepared-statements: false:除非大量重复 SQL,否则关掉
11.3 连接数计算公式
HikariCP 官方推荐:
maximum-pool-size = (core_count * 2) + effective_spindle_count
core_count:CPU 核心数effective_spindle_count:磁盘数(SSD 算 1)
例如 8 核 CPU + SSD:
maximum-pool-size = 8 * 2 + 1 = 17 ≈ 20
实测结论:20 个连接的性能比 50 个连接更好——连接少了不够用,多了反而有锁竞争。
十二、常见误区
12.1 误区一:连接池越大越好
# ❌ 错误
hikari.maximum-pool-size: 200
连接池太大:
- 数据库压力增大(每个连接占内存)
- MySQL
max_connections容易打满 - 锁竞争加剧
正确:20-50 够用。
12.2 误区二:minimum-idle 设成 0
# ❌ 不推荐
hikari.minimum-idle: 0
空闲时连接全部销毁,流量来了要重建:
- 建连耗时 50-100ms
- 首批请求变慢
正确:minimum-idle = maximum-pool-size,固定大小。
12.3 误区三:不配 max-lifetime
# ❌ 危险
# 不配 max-lifetime,连接会一直用
MySQL wait_timeout 默认 8 小时,如果连接超过这个时间不用,MySQL 会主动断开。
应用拿到一个已经断开的连接,执行报错。
正确:max-lifetime 设为 wait_timeout - 1 分钟。
12.4 误区四:开启 test-on-borrow
# ❌ 影响性能
druid.test-on-borrow: true
每次借连接都校验,QPS 降 30-50%。
正确:test-while-idle: true 足够(空闲时校验)。
十三、总结
压测数据汇总
| 维度 | HikariCP | Druid | DBCP2 |
|---|---|---|---|
| 获取连接 P99 | 0.71ms | 2.85ms | 5.43ms |
| 真实查询 QPS | 26,315 | 16,129 | 10,526 |
| 内存占用 | 12MB | 38MB | 25MB |
| Young GC/10min | 4 次 | 15 次 | 11 次 |
| SQL 监控 | ❌ | ⭐⭐⭐⭐⭐ | ❌ |
| 连接泄漏检测 | 日志 | 日志+回收 | 日志+回收 |
| 配置项 | 10 | 30+ | 20 |
一句话总结
| 连接池 | 一句话 |
|---|---|
| HikariCP | 性能王者,Spring Boot 默认,绝大多数场景首选 |
| Druid | 监控之王,企业级开发利器,但性能代价不小 |
| DBCP2 | 老牌选手,稳定可靠,新项目没必要选 |
生产最佳实践
推荐方案:HikariCP 做连接池 + Druid Filter 做 SQL 监控
连接数:maximum-pool-size = 20(8 核推荐)
最小空闲:minimum-idle = maximum-pool-size
最大生命周期:max-lifetime = 30min
校验方式:test-while-idle(不用 test-on-borrow)
泄漏检测:leak-detection-threshold = 60s
选择决策树
是否需要极致性能?
├── 是 → HikariCP
│ └── 是否需要 SQL 监控?
│ ├── 是 → HikariCP + Druid StatFilter
│ └── 否 → HikariCP(+ Actuator 指标)
└── 否 → 是否需要强监控?
├── 是 → Druid
└── 否 → DBCP2(老项目维护)
互动话题:你的项目用的是哪个连接池?有没有踩过连接泄漏的坑?欢迎留言讨论!
参考资料
标题:数据库连接池巅峰对决:HikariCP vs Druid vs DBCP2——10,000 并发实测
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/08/08/1786158929019.html
公众号:服务端技术精选
- 引言
- 一、三位选手介绍
- 1.1 HikariCP
- 1.2 Druid
- 1.3 DBCP2
- 1.4 三者定位对比
- 二、压测环境统一
- 2.1 硬件配置
- 2.2 统一连接池配置
- 2.3 压测场景
- 三、压测结果:纯性能对决
- 3.1 场景一:获取连接耗时
- 3.2 场景二:真实查询吞吐量
- 3.3 内存占用
- 3.4 GC 压力对比
- 四、深度分析:HikariCP 凭什么这么快
- 4.1 ConcurrentBag:无锁连接获取
- 4.2 字节码优化
- 4.3 精简代码
- 4.4 快速失败
- 五、Druid 凭什么监控最强
- 5.1 内置监控功能
- 5.2 SQL 监控详解
- 5.3 连接泄漏检测
- 5.4 性能代价
- 六、DBCP2:老骥伏枥
- 6.1 优势
- 6.2 劣势
- 6.3 适用场景
- 七、配置易用性对比
- 7.1 HikariCP 配置
- 7.2 Druid 配置
- 7.3 DBCP2 配置
- 7.4 对比
- 八、连接泄漏检测对比
- 8.1 测试方法
- 8.2 三个连接池的表现
- 8.3 结论
- 九、SQL 监控能力对比
- 9.1 功能对比
- 9.2 如果用 HikariCP 又想要监控
- 十、生产推荐方案
- 10.1 推荐方案:HikariCP + Druid Filter
- 10.2 不同场景的选择
- 10.3 为什么不直接用 Druid 连接池
- 十一、调优建议
- 11.1 HikariCP 调优
- 11.2 Druid 调优
- 11.3 连接数计算公式
- 十二、常见误区
- 12.1 误区一:连接池越大越好
- 12.2 误区二:minimum-idle 设成 0
- 12.3 误区三:不配 max-lifetime
- 12.4 误区四:开启 test-on-borrow
- 十三、总结
- 压测数据汇总
- 一句话总结
- 生产最佳实践
- 选择决策树
- 参考资料
评论