1. 为什么需要连接池
1.1 数据库连接的代价
创建一条数据库连接是昂贵的操作:
- TCP 三次握手(~1-3ms,跨可用区更长)
- TLS /SSL 握手(如有,~5-20ms)
- 数据库认证(密码验证、权限查询)
- 内存分配(数据库端为连接分配会话内存)
无连接池 vs 连接池:
无连接池:
每次请求 ──→ 新建连接 ──→ 执行 SQL ──→ 关闭连接
↑ ↓
└──── 每次 5-20ms ────┘
连接池:
初始化:创建 10 条连接放入池中
每次请求 ──→ 从池获取连接(< 1ms)──→ 执行 SQL ──→ 归还连接
↑ ↓
└────────── 复用,无创建开销 ──────────┘
1.2 连接池核心收益
| 收益 | 说明 |
|---|---|
| 降低延迟 | 避免每次请求建立连接的开销 |
| 资源控制 | 限制最大连接数,防止数据库过载 |
| 连接复用 | 减少数据库端会话资源消耗 |
| 稳定性 | 预热连接、健康检查保障可用性 |
2. 连接池核心参数
2.1 参数详解
| 参数 | 作用 | 建议值 |
|---|---|---|
| minimumIdle | 最小空闲连接数 | 5-10(保持热连接) |
| maximumPoolSize | 最大连接数 | 20-50(取决于 DB 配置) |
| connectionTimeout | 获取连接最大等待时间 | 30s |
| idleTimeout | 空闲连接超时回收 | 10min |
| maxLifetime | 连接最大存活时间 | 30min(短于 DB wait_timeout) |
| keepaliveTime | 保活检查间隔 | 5min |
2.2 最大连接数设置
公式:连接池大小 = ((核心数 × 2) + 有效磁盘数)
更精确的计算:
连接数 = (CPU 核心数 × 2) + 磁盘 spindle 数
示例(HikariCP 作者建议):
4 核服务器 → 约 10 个连接即可获得最大吞吐
连接数再多,由于上下文切换和锁竞争,吞吐反而下降
生产环境经验:
单机应用:10-20 连接
微服务集群(10 实例 × 10 连接):需 DB 支持 100+ 连接
MySQL max_connections 默认 151,需按规模调整
3. 主流连接池对比
3.1 HikariCP(推荐)
“Fast, simple, reliable. Zero overhead.”
| 特性 | HikariCP |
|---|---|
| 特点 | 极致性能、代码精简、无额外功能 |
| 性能 | 最快(benchmark 中远超 Druid/c3p0) |
| 监控 | 基础 JMX 指标 |
| 防护 | 无 SQL 注入检测、无防火墙 |
// Spring Boot 默认连接池(2.0+)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/shop");
config.setUsername("user");
config.setPassword("pass");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setLeakDetectionThreshold(60000); // 连接泄露检测:60s未归还则记录
HikariDataSource dataSource = new HikariDataSource(config);
3.2 Druid(阿里开源)
“为监控而生的数据库连接池”
| 特性 | Druid |
|---|---|
| 特点 | 功能丰富、监控完善、内置 SQL 防火墙 |
| 性能 | 接近 HikariCP |
| 监控 | 完整 Web UI、SQL 统计、慢查询 |
| 防护 | SQL 注入检测、黑白名单、WallFilter |
// Druid 配置示例
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/shop");
dataSource.setInitialSize(5);
dataSource.setMinIdle(5);
dataSource.setMaxActive(20);
dataSource.setMaxWait(30000);
// 开启监控
DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
// SQL 防火墙
WallConfig wallConfig = new WallConfig();
wallConfig.setSelectAllow(true);
wallConfig.setDeleteAllow(false); // 禁止 DELETE
3.3 连接池对比
| 维度 | HikariCP | Druid | c3p0 | DBCP2 |
|---|---|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| 监控能力 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| SQL 防护 | ❌ | ✅ | ❌ | ❌ |
| 代码体积 | 极小 | 中等 | 中等 | 小 |
| 活跃度 | 高 | 高 | 低 | 中 |
| Spring Boot 默认 | ✅ (2.0+) | ❌ | ❌ | ❌ |
4. 连接泄露与治理
4.1 连接泄露场景
// ❌ 错误:未关闭连接
public void badMethod() {
Connection conn = dataSource.getConnection();
// 执行业务逻辑...
// 忘记 conn.close()!
}
// ✅ 正确:try-with-resources
public void goodMethod() {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(SQL)) {
// 执行 SQL
} catch (SQLException e) {
// 异常处理
}
// 自动关闭 Connection 和 Statement
}
4.2 连接泄露检测
// HikariCP 泄露检测
config.setLeakDetectionThreshold(60000); // 60s
// 检测到泄露时输出:
// Apparent connection leak detected, owning stack trace follows:
// java.lang.Thread.getStackTrace(Thread.java:1559)
// com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
// com.example.Service.query(Service.java:45)
5. 生产实践
5.1 连接池配置模板
# application.yml (Spring Boot)
spring:
datasource:
url: jdbc:mysql://mysql-service:3306/shop?useSSL=true
username: ${DB_USER}
password: ${DB_PASS}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: ShopHikariPool
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000
connection-test-query: SELECT 1
validation-timeout: 5000
5.2 监控指标
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| Active Connections | 活跃连接数 | > maximumPoolSize × 80% |
| Idle Connections | 空闲连接数 | < minimumIdle |
| Pending Threads | 等待连接的线程数 | > 0 持续 1 分钟 |
| Total Connections | 总连接数 | 异常波动 |
| Connection Acquired Time | 获取连接耗时 | > 100ms |
6. 连接池监控与故障排查
6.1 HikariCP 指标解析
HikariCP 通过 JMX/Micrometer 暴露丰富的运行时指标:
| 指标名 | 含义 | 正常范围 | 异常诊断 |
|---|---|---|---|
ActiveConnections | 当前借出连接数 | < max × 80% | 持续高位 → 连接泄露或慢查询 |
IdleConnections | 池中空闲连接 | ≥ minIdle | 长期为 0 → 流量突增或池过小 |
PendingThreads | 等待获取连接的线程 | = 0 | > 0 → 连接不足或获取超时 |
TotalConnections | 池内总连接数 | min~max 之间 | 异常抖动 → 网络不稳定 |
ConnectionTimeoutRate | 获取超时率 | ≈ 0% | > 1% → 需扩容或查慢SQL |
UsageMillis | 连接平均使用时长 | < 100ms | > 1s → 存在未关闭 Statement |
// Micrometer + Prometheus 暴露连接池指标
@Configuration
public class DataSourceMetricsConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> hikariMetrics(
HikariDataSource dataSource) {
return registry -> {
registry.gauge("hikari.connections.active",
Tags.of("pool", dataSource.getPoolName()),
dataSource, ds -> ds.getHikariPoolMXBean().getActiveConnections());
registry.gauge("hikari.connections.idle",
Tags.of("pool", dataSource.getPoolName()),
dataSource, ds -> ds.getHikariPoolMXBean().getIdleConnections());
};
}
}
6.2 连接泄露排查实战
当 leakDetectionThreshold 触发时,日志会打印获取连接的堆栈:
Apparent connection leak detected, owning stack trace follows:
java.lang.Thread.getStackTrace(Thread.java:1559)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
at com.example.OrderService.queryOrder(OrderService.java:45) ← 代码位置
...
常见泄露根因:
| 根因 | 代码特征 | 修复方式 |
|---|---|---|
| 忘记 close | 无 try-with-resources | 全部改造 |
| 异常路径未关闭 | catch 块中无 finally | try-with-resources |
| 静态持有 Connection | static Connection conn | 每次从池获取 |
| 长事务未提交 | 事务开启后阻塞等待 | 设置事务超时 |
| ORM 缓存未刷新 | Hibernate 二级缓存 | flush() + clear() |
6.3 数据库端连接观察
-- MySQL:查看当前连接来源与状态
SELECT
id, user, host, db, command, time, state,
LEFT(info, 100) as query_preview
FROM information_schema.processlist
WHERE command != 'Sleep'
ORDER BY time DESC;
-- PostgreSQL:按应用名聚合连接
SELECT
application_name,
COUNT(*) as conn_count,
MAX(EXTRACT(EPOCH FROM NOW() - backend_start)) as oldest_sec
FROM pg_stat_activity
WHERE datname = 'mydb'
GROUP BY application_name;
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。