连接池优化
面试高频考点:连接池原理、HikariCP 配置、Druid 配置、参数调优策略
一、连接池基础
为什么需要连接池?
没有连接池时:
每次数据库操作:
1. TCP 三次握手建立连接(~10ms)
2. MySQL 认证(~5ms)
3. 执行 SQL(~1ms)
4. 关闭连接(~5ms)
5. TCP 四次挥手(~10ms)
总耗时:~31ms,实际业务只占 1ms使用连接池后:
应用启动时:
1. 预创建 N 个连接
每次数据库操作:
1. 从池中获取连接(<1ms)
2. 执行 SQL(~1ms)
3. 归还连接到池(<1ms)
总耗时:~3ms,提升 10 倍连接池核心原理
┌─────────────────────────────────────────────────────────────┐
│ 连接池 │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │连接1│ │连接2│ │连接3│ │连接4│ │连接5│ ← 活跃连接池 │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ 应用请求连接 → 池中有空闲连接? → 是 → 返回连接 │
│ ↓ │
│ 否 │
│ ↓ │
│ 连接数 < 最大值? → 是 → 创建新连接 │
│ ↓ │
│ 否 │
│ ↓ │
│ 等待超时 → 抛出异常 │
└─────────────────────────────────────────────────────────────┘连接池核心参数
| 参数 | 说明 | 推荐值 |
|---|---|---|
| minimumIdle | 最小空闲连接数 | 与 maximumPoolSize 相同 |
| maximumPoolSize | 最大连接数 | CPU 核数 * 2 + 有效磁盘数 |
| idleTimeout | 空闲连接超时时间 | 600000ms (10分钟) |
| maxLifetime | 连接最大存活时间 | 1800000ms (30分钟) |
| connectionTimeout | 获取连接超时时间 | 30000ms (30秒) |
| connectionTestQuery | 连接测试 SQL | SELECT 1 |
二、HikariCP 连接池
HikariCP 是 Spring Boot 2.x 默认的连接池,以高性能著称。
为什么 HikariCP 快?
1. 字节码级别优化
- 使用 Javassist 生成代理类
- 减少方法调用层级
2. 数据结构优化
- ConcurrentBag 替代 BlockingQueue
- 无锁设计,减少竞争
3. 代理优化
- 直接代理 Connection,不包装 Statement
- 减少对象创建
4. 连接验证优化
- 使用 JDBC4 的 isValid() 方法
- 避免执行测试 SQLHikariCP 配置
Spring Boot 配置:
# application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: password
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 连接池名称
pool-name: MyHikariPool
# 连接数配置(核心)
minimum-idle: 10 # 最小空闲连接
maximum-pool-size: 20 # 最大连接数
# 超时配置
idle-timeout: 600000 # 空闲超时 10 分钟
max-lifetime: 1800000 # 最大存活 30 分钟
connection-timeout: 30000 # 获取超时 30 秒
# 连接验证
connection-test-query: SELECT 1 # 测试 SQL(JDBC4 可不配)
validation-timeout: 3000 # 验证超时 3 秒
# 泄漏检测
leak-detection-threshold: 60000 # 泄漏检测阈值 60 秒
# 只读配置
read-only: false
# 注册 MBean(JMX 监控)
register-mbeans: trueJava 配置:
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
// 或手动配置
@Bean
public DataSource hikariDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
// 连接池配置
config.setMinimumIdle(10);
config.setMaximumPoolSize(20);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setConnectionTimeout(30000);
config.setPoolName("MyHikariPool");
// 性能优化
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
return new HikariDataSource(config);
}
}HikariCP 监控
@RestController
public class PoolController {
@Autowired
private DataSource dataSource;
@GetMapping("/pool/stats")
public Map<String, Object> poolStats() {
HikariDataSource ds = (HikariDataSource) dataSource;
HikariPoolMXBean pool = ds.getHikariPoolMXBean();
Map<String, Object> stats = new HashMap<>();
stats.put("activeConnections", pool.getActiveConnections());
stats.put("idleConnections", pool.getIdleConnections());
stats.put("totalConnections", pool.getTotalConnections());
stats.put("threadsAwaitingConnection", pool.getThreadsAwaitingConnection());
return stats;
}
}三、Druid 连接池
Druid 是阿里巴巴开源的连接池,监控功能丰富。
Druid 配置
Spring Boot 配置:
# application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: password
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 连接池配置
initial-size: 10 # 初始连接数
min-idle: 10 # 最小空闲连接
max-active: 20 # 最大连接数
max-wait: 60000 # 获取连接超时
# 连接检测
validation-query: SELECT 1 # 测试 SQL
test-while-idle: true # 空闲时检测
test-on-borrow: false # 获取时检测(影响性能)
test-on-return: false # 归还时检测(影响性能)
# 空闲连接检测
time-between-eviction-runs-millis: 60000 # 检测间隔
min-evictable-idle-time-millis: 300000 # 最小空闲时间
# 连接保活
keep-alive: true # 保持连接活跃
phy-max-use-count: 1000 # 物理连接最大使用次数
# 监控配置
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
reset-enable: false
# Web 监控
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
# SQL 监控
filter:
stat:
enabled: true
log-slow-sql: true # 记录慢 SQL
slow-sql-millis: 3000 # 慢 SQL 阈值
merge-sql: true
wall:
enabled: true # 防火墙
config:
multi-statement-allow: trueJava 配置:
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource druidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
// 基本配置
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("password");
// 连接池配置
dataSource.setInitialSize(10);
dataSource.setMinIdle(10);
dataSource.setMaxActive(20);
dataSource.setMaxWait(60000);
// 连接检测
dataSource.setValidationQuery("SELECT 1");
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(false);
dataSource.setTestOnReturn(false);
// 空闲检测
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
// 开启监控
try {
dataSource.setFilters("stat,wall");
} catch (SQLException e) {
e.printStackTrace();
}
return dataSource;
}
// 配置监控页面
@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
bean.addInitParameter("loginUsername", "admin");
bean.addInitParameter("loginPassword", "admin");
bean.addInitParameter("resetEnable", "false");
return bean;
}
// 配置 Web 监控
@Bean
public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {
FilterRegistrationBean<WebStatFilter> bean =
new FilterRegistrationBean<>(new WebStatFilter());
bean.addUrlPatterns("/*");
bean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return bean;
}
}Druid 监控功能
访问 http://localhost:8080/druid/ 可以看到:
1. 数据源信息
- 活跃连接数
- 空闲连接数
- 连接池配置
2. SQL 监控
- 执行次数
- 执行时间
- 最慢 SQL
- 错误次数
3. SQL 防火墙
- 危险 SQL 拦截
- SQL 统计
4. Web 应用监控
- URI 监控
- Session 监控
- Spring 监控四、HikariCP vs Druid
| 特性 | HikariCP | Druid |
|---|---|---|
| 性能 | 最快 | 较快 |
| 监控 | 基础(JMX) | 丰富(Web UI) |
| SQL 监控 | 无 | 有 |
| 慢 SQL 记录 | 无 | 有 |
| SQL 防火墙 | 无 | 有 |
| 配置复杂度 | 简单 | 复杂 |
| 社区活跃度 | 高 | 高(阿里维护) |
| Spring Boot 默认 | 是 | 否 |
选择建议:
- 追求性能:HikariCP
- 需要监控:Druid
- 生产环境推荐:HikariCP + Prometheus + Grafana
五、连接数计算公式
最大连接数计算
最大连接数 = (CPU 核数 * 2) + 有效磁盘数
解释:
- CPU 核数 * 2:充分利用 CPU(线程数)
- 有效磁盘数:并发 I/O 能力
示例:
- 8 核 CPU,1 块磁盘:最大连接数 = 8 * 2 + 1 = 17
- 16 核 CPU,RAID 10(4 块盘):最大连接数 = 16 * 2 + 4 = 36注意:这是理论值,实际需要根据数据库服务器配置和负载调整。
连接数评估方法
// 监控连接池使用情况,获取实际需要的连接数
@RestController
public class PoolMonitor {
@GetMapping("/pool/recommend")
public String recommendPoolSize() {
// 1. 观察高峰期活跃连接数
// 2. maximumPoolSize = 高峰活跃数 * 1.5
// 3. minimumIdle = 平均活跃数
// 示例:高峰期活跃 15,平均活跃 8
// maximumPoolSize = 15 * 1.5 = 22
// minimumIdle = 8
return "Observe active connections during peak hours";
}
}六、连接泄漏排查
现象
- 连接池连接数逐渐增加
- 最终达到最大值,无法获取新连接
- 应用报错:Connection is not availableHikariCP 泄漏检测
spring:
datasource:
hikari:
leak-detection-threshold: 60000 # 60 秒未归还视为泄漏日志输出:
Apparent connection leak detected. Connection has been out of pool for 60000ms.常见泄漏原因
// 问题 1:忘记关闭连接
public void badMethod() throws SQLException {
Connection conn = dataSource.getConnection();
// 业务逻辑...
// 忘记 conn.close()
}
// 修复:使用 try-with-resources
public void goodMethod() throws SQLException {
try (Connection conn = dataSource.getConnection()) {
// 业务逻辑...
}
}
// 问题 2:异常时未关闭
public void badMethod() throws SQLException {
Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
stmt.execute("..."); // 这里抛异常,连接未关闭
conn.close();
}
// 修复
public void goodMethod() throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("...");
}
}
// 问题 3:事务未提交/回滚
@Transactional
public void badMethod() {
// 业务逻辑...
throw new RuntimeException(); // 事务未正确处理
}
// 修复:确保事务正确管理
@Transactional(rollbackFor = Exception.class)
public void goodMethod() {
// 业务逻辑...
}七、连接池监控指标
关键监控指标
1. 活跃连接数 (active_connections)
- 当前正在使用的连接数
- 持续接近最大值需扩容
2. 空闲连接数 (idle_connections)
- 当前空闲的连接数
- 持续为 0 需扩容
3. 等待获取连接的线程数 (threads_awaiting)
- 等待连接的请求数
- 大于 0 说明连接池不足
4. 获取连接平均时间 (connection_acquire_time)
- 从请求到获取连接的时间
- 过高说明连接池压力大
5. 连接使用时间 (connection_usage_time)
- 连接被占用的平均时间
- 过高可能有慢 SQLPrometheus + Grafana 监控
# pom.xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
export:
prometheus:
enabled: true访问指标:http://localhost:8080/actuator/prometheus
关键指标:
# 活跃连接数
hikaricp_connections_active{pool="MyHikariPool"}
# 空闲连接数
hikaricp_connections_idle{pool="MyHikariPool"}
# 最大连接数
hikaricp_connections_max{pool="MyHikariPool"}
# 获取连接时间
hikaricp_connections_creation_seconds_sum八、面试要点
Q1: 为什么需要连接池?
回答要点:
- 避免频繁创建/销毁连接的开销(TCP 连接、MySQL 认证)
- 复用连接,提高性能
- 控制并发连接数,保护数据库
- 提供连接监控和管理功能
Q2: HikariCP 为什么快?
回答要点:
- 字节码级别优化(Javassist 生成代理类)
- ConcurrentBag 无锁设计
- 直接代理 Connection,减少对象创建
- 使用 JDBC4 isValid() 避免测试 SQL
Q3: 如何确定连接池大小?
回答要点:
// 公式
maximumPoolSize = CPU核数 * 2 + 有效磁盘数
// 实际评估
1. 观察高峰期活跃连接数
2. maximumPoolSize = 高峰活跃数 * 1.5
3. minimumIdle = 平均活跃数
4. 根据监控数据持续优化Q4: 连接泄漏如何排查?
回答要点:
- 开启泄漏检测:leak-detection-threshold
- 使用 try-with-resources 确保连接关闭
- 检查事务是否正确提交/回滚
- 使用 Druid 监控查看活跃连接
Q5: HikariCP 和 Druid 如何选择?
回答要点:
| 场景 | 选择 |
|---|---|
| 追求极致性能 | HikariCP |
| 需要丰富监控 | Druid |
| 需要慢 SQL 分析 | Druid |
| Spring Boot 项目 | HikariCP(默认) |
小结
- 连接池复用连接,避免频繁创建/销毁
- HikariCP 性能最优,Spring Boot 默认
- Druid 监控丰富,适合需要 SQL 分析场景
- 连接数公式:CPU核数 * 2 + 有效磁盘数
- 开启泄漏检测,使用 try-with-resources
- 监控关键指标:活跃/空闲连接数、等待线程数