数据库连接池详解
本章导读
数据库连接池是 Java 应用与数据库交互的核心组件,直接影响系统的性能和稳定性。本章深入讲解主流连接池 HikariCP 和 Druid 的原理、配置参数、监控方案和调优技巧,帮助你在生产环境中正确配置和优化连接池,避免连接泄露、超时等常见问题。
学习目标:
- 目标1:理解连接池的工作原理和核心参数的含义
- 目标2:掌握 HikariCP 和 Druid 的配置方法,能够根据业务场景选择合适的连接池
- 目标3:具备连接池监控和故障排查能力,能够诊断和解决连接泄露、超时等问题
前置知识:JDBC 基础、数据库基础、多线程编程
阅读时长:约 45 分钟
一、知识概述
数据库连接池是 Java 应用与数据库交互的核心组件,它通过预先创建并管理数据库连接,避免了每次请求都创建新连接的开销,显著提升了系统性能。在高并发场景下,连接池的配置和优化直接影响系统的吞吐量和响应时间。
本文将深入讲解主流数据库连接池 HikariCP、Druid 的原理和配置,连接池核心参数调优,监控与故障排查,帮助你在实际项目中正确配置和优化连接池。
二、连接池核心原理
2.1 为什么需要连接池
/**
* 不使用连接池的问题
*/
public class WithoutConnectionPool {
/**
* 每次请求都创建新连接
*/
public User getUser(Long id) throws SQLException {
// 1. 加载驱动
// 2. 建立连接(TCP 三次握手、SSL 握手、数据库认证)
// 3. 执行查询
// 4. 关闭连接
// 问题:
// - 创建连接开销大(10-100ms)
// - 频繁创建/销毁连接消耗资源
// - 数据库连接数有限
// - 高并发时数据库压力巨大
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/db", "root", "123456");
try {
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM user WHERE id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
// 处理结果...
return null;
} finally {
conn.close(); // 关闭连接
}
}
/**
* 连接创建开销分析
*/
public void connectionCostAnalysis() {
// 创建一个数据库连接的开销:
// 1. TCP 三次握手:1-3ms
// 2. SSL/TLS 握手(如果启用):5-10ms
// 3. MySQL 认证:2-5ms
// 4. 初始化连接参数:1-2ms
// 总计:10-20ms(本地网络)
//
// 而执行一条简单 SQL 只需 1-5ms
//
// 使用连接池后:
// - 获取连接:< 1ms
// - 执行 SQL:1-5ms
// - 归还连接:< 1ms
}
}
/**
* 使用连接池
*/
public class WithConnectionPool {
private DataSource dataSource;
public User getUser(Long id) throws SQLException {
// 从连接池获取连接(复用已有连接)
try (Connection conn = dataSource.getConnection()) {
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM user WHERE id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
// 处理结果...
return null;
} // 归还连接到连接池(不是关闭)
}
}
2.2 连接池工作原理
/**
* 连接池核心原理示意
*/
public class ConnectionPoolPrinciple {
/**
* 简化的连接池实现
*/
public static class SimpleConnectionPool {
// 空闲连接队列
private final BlockingQueue<Connection> idleConnections;
// 活跃连接集合
private final Set<Connection> activeConnections;
// 最大连接数
private final int maxPoolSize;
// 数据库配置
private final String url;
private final String username;
private final String password;
public Connection getConnection() throws SQLException {
// 1. 尝试从空闲队列获取
Connection conn = idleConnections.poll();
if (conn != null) {
// 2. 验证连接有效性
if (isConnectionValid(conn)) {
activeConnections.add(conn);
return conn;
}
}
// 3. 如果未达到最大连接数,创建新连接
if (activeConnections.size() < maxPoolSize) {
conn = createNewConnection();
activeConnections.add(conn);
return conn;
}
// 4. 等待其他线程归还连接
try {
conn = idleConnections.take(); // 阻塞等待
activeConnections.add(conn);
return conn;
} catch (InterruptedException e) {
throw new SQLException("获取连接超时");
}
}
public void releaseConnection(Connection conn) {
if (conn != null) {
activeConnections.remove(conn);
// 检查连接是否有效
if (isConnectionValid(conn)) {
idleConnections.offer(conn); // 归还到空闲队列
} else {
closeConnection(conn); // 关闭无效连接
}
}
}
private boolean isConnectionValid(Connection conn) {
try {
return conn != null && !conn.isClosed() && conn.isValid(1);
} catch (SQLException e) {
return false;
}
}
private Connection createNewConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
private void closeConnection(Connection conn) {
try {
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException ignored) {
}
}
}
/**
* 连接池工作流程
*/
/*
┌─────────────────────────────────────────────────────────────────────────┐
│ 应用线程 │
│ │ │
│ ▼ │
│ getConnection() │
│ │ │
└──────────────────────────────┼──────────────────────────────────────────┘
│
┌──────────────────────────────┼──────────────────────────────────────────┐
│ 连接池核心逻辑 │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 空闲连接池 │ │ 活跃连接池 │ │ 创建连接 │ │
│ │ idleQueue │ │ activeSet │ │ (新建) │ │
│ │ │ │ │ │ │ │
│ │ conn1 │ │ conn5 │ │ │ │
│ │ conn2 │ │ conn6 │ │ │ │
│ │ conn3 │ │ conn7 │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ 返回连接给应用 │
│ │ │
└──────────────────────────────┼──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 数据库 │
│ MySQL / PostgreSQL / Oracle │
└─────────────────────────────────────────────────────────────────────────┘
*/
}
三、HikariCP 连接池
3.1 HikariCP 简介
/**
* HikariCP - 高性能 JDBC 连接池
*
* 特点:
* 1. 性能极高(号称"史上最快")
* 2. 代码简洁(约 130KB)
* 3. Spring Boot 2.x 默认连接池
* 4. 零依赖
*/
3.2 基础配置
# application.yml - HikariCP 配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# HikariCP 配置
hikari:
# 连接池名称(便于监控识别)
pool-name: MyHikariPool
# 最小空闲连接数
minimum-idle: 10
# 最大连接数
maximum-pool-size: 50
# 连接超时时间(毫秒)
connection-timeout: 30000
# 空闲连接存活最大时间(毫秒)
idle-timeout: 600000
# 连接最大存活时间(毫秒)
max-lifetime: 1800000
# 连接测试查询
connection-test-query: SELECT 1
# 连接泄露检测阈值(毫秒)
leak-detection-threshold: 60000
# 验证超时时间(毫秒)
validation-timeout: 5000
# 只读配置
read-only: false
# 自动提交
auto-commit: true
# 数据源属性
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
useLocalSessionState: true
cacheResultSetMetadata: true
cacheServerConfiguration: true
elideSetAutoCommits: true
maintainTimeStats: false
3.3 Java 配置方式
/**
* HikariCP Java 配置
*/
@Configuration
public class HikariConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// 基本配置
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 连接池配置
dataSource.setPoolName("MyHikariPool");
dataSource.setMinimumIdle(10);
dataSource.setMaximumPoolSize(50);
dataSource.setConnectionTimeout(30000);
dataSource.setIdleTimeout(600000);
dataSource.setMaxLifetime(1800000);
// 连接测试
dataSource.setConnectionTestQuery("SELECT 1");
// 泄露检测
dataSource.setLeakDetectionThreshold(60000);
// MySQL 优化配置
dataSource.addDataSourceProperty("cachePrepStmts", "true");
dataSource.addDataSourceProperty("prepStmtCacheSize", "250");
dataSource.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource.addDataSourceProperty("useServerPrepStmts", "true");
return dataSource;
}
}
/**
* 多数据源 HikariCP 配置
*/
@Configuration
public class MultiHikariConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary.hikari")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
3.4 参数详解
/**
* HikariCP 参数详解
*/
public class HikariParametersExplanation {
/**
* 1. 连接池大小配置
*/
// minimumIdle: 最小空闲连接数
// - 默认与 maximumPoolSize 相同
// - 建议设置为与 maximumPoolSize 相同,避免频繁创建销毁
// - 如果设置为较小值,空闲时会减少连接数以节省资源
// maximumPoolSize: 最大连接数
// - 默认 10
// - 公式:(核心数 * 2) + 有效磁盘数
// - 例如:4 核 CPU + 1 块 SSD = 4 * 2 + 1 = 9
// - 不是越大越好,过多的连接会增加数据库负担
/**
* 2. 超时配置
*/
// connectionTimeout: 等待连接的最大毫秒数
// - 默认 30000(30秒)
// - 超时后抛出 SQLException
// - 建议根据业务响应时间设置
// idleTimeout: 空闲连接存活时间
// - 默认 600000(10分钟)
// - 只有当 minimumIdle < maximumPoolSize 时生效
// - 空闲连接超过此时间会被关闭
// maxLifetime: 连接最大存活时间
// - 默认 1800000(30分钟)
// - 建议比数据库 wait_timeout 小
// - MySQL 默认 wait_timeout=8小时
// validationTimeout: 验证连接有效性的超时时间
// - 默认 5000(5秒)
/**
* 3. 连接测试
*/
// connectionTestQuery: 连接测试查询
// - 默认不设置,使用 JDBC4 的 isValid() 方法
// - 如果驱动不支持 JDBC4,需要设置
// - 设置后会降级性能,推荐使用 JDBC4 驱动
/**
* 4. 泄露检测
*/
// leakDetectionThreshold: 连接泄露检测阈值
// - 默认 0(不检测)
// - 设置后,连接被持有超过此时间会打印警告日志
// - 建议开发环境设置,生产环境可关闭或设置较大值
// - 典型值:60000(60秒)
/**
* 5. 数据源属性
*/
// 针对特定数据库的优化配置
// MySQL 推荐配置:
// - cachePrepStmts: 缓存 PreparedStatement
// - prepStmtCacheSize: 缓存大小
// - prepStmtCacheSqlLimit: 缓存 SQL 最大长度
// - useServerPrepStmts: 使用服务器端预编译
/**
* 计算连接池大小
*/
public void calculatePoolSize() {
// PostgreSQL 官方推荐公式:
// connections = (core_count * 2) + effective_spindle_count
// 例如:
// - 8 核 CPU
// - 1 块 SSD
// - connections = 8 * 2 + 1 = 17
// 实际调优步骤:
// 1. 使用公式计算初始值
// 2. 压测观察连接等待情况
// 3. 监控数据库连接数
// 4. 根据实际情况微调
}
}
3.5 HikariCP 监控
/**
* HikariCP 监控配置
*/
@Configuration
public class HikariMonitorConfig {
/**
* JMX 监控
*/
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// ... 其他配置
// 启用 JMX(默认启用)
dataSource.setRegisterMbeans(true);
return dataSource;
}
/**
* 通过 JMX 获取连接池状态
*/
// 使用 JConsole 或 VisualVM 连接应用
// 查看 MBean: com.zaxxer.hikari -> pool name -> PoolConfig / PoolStats
/**
* 代码方式获取连接池状态
*/
@Service
public class HikariMonitorService {
@Autowired
private DataSource dataSource;
public HikariPoolStatus getPoolStatus() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
HikariPoolMXBean poolProxy = hikariDataSource.getHikariPoolMXBean();
if (poolProxy != null) {
HikariPoolStatus status = new HikariPoolStatus();
status.setActiveConnections(poolProxy.getActiveConnections());
status.setIdleConnections(poolProxy.getIdleConnections());
status.setTotalConnections(poolProxy.getTotalConnections());
status.setThreadsAwaitingConnection(
poolProxy.getThreadsAwaitingConnection());
return status;
}
}
return null;
}
/**
* 定时打印连接池状态
*/
@Scheduled(fixedRate = 5000)
public void logPoolStatus() {
HikariPoolStatus status = getPoolStatus();
if (status != null) {
log.info("HikariPool Status: active={}, idle={}, total={}, waiting={}",
status.getActiveConnections(),
status.getIdleConnections(),
status.getTotalConnections(),
status.getThreadsAwaitingConnection());
}
}
}
/**
* 连接池状态 DTO
*/
@Data
public static class HikariPoolStatus {
private int activeConnections;
private int idleConnections;
private int totalConnections;
private int threadsAwaitingConnection;
}
/**
* 自定义指标(Micrometer 集成)
*/
@Bean
public MeterBinder hikariMetrics(DataSource dataSource) {
return (registry) -> {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
// 注册 HikariCP 指标
new HikariDataSourceMetrics(hikariDataSource, "my-pool",
Collections.emptyList()).bindTo(registry);
}
};
}
}
四、Druid 连接池
4.1 Druid 简介
/**
* Druid - 阿里巴巴数据库连接池
*
* 特点:
* 1. 监控功能强大
* 2. SQL 监控和防火墙
* 3. 防止 SQL 注入
* 4. 扩展性好
*/
4.2 基础配置
# application.yml - Druid 配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
druid:
# 连接池配置
initial-size: 10 # 初始连接数
min-idle: 10 # 最小空闲连接
max-active: 50 # 最大连接数
# 超时配置
max-wait: 60000 # 获取连接最大等待时间(毫秒)
time-between-eviction-runs-millis: 60000 # 检测间隔时间
min-evictable-idle-time-millis: 300000 # 连接最小空闲时间
# 连接验证
validation-query: SELECT 1 # 验证 SQL
validation-query-timeout: 5 # 验证超时(秒)
test-while-idle: true # 空闲时检测
test-on-borrow: false # 获取时检测
test-on-return: false # 归还时检测
# 连接保持
keep-alive: true # 保持连接活跃
phy-max-use-count: 1000 # 物理连接最大使用次数
# 预编译缓存
pool-prepared-statements: true # 开启 PreparedStatement 缓存
max-pool-prepared-statement-per-connection-size: 20
# 监控配置
stat-view-servlet:
enabled: true # 启用监控页面
url-pattern: /druid/* # 访问路径
login-username: admin # 用户名
login-password: admin # 密码
reset-enable: false # 禁用重置按钮
web-stat-filter:
enabled: true # 启用 Web 监控
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 # 合并相同 SQL
wall: # SQL 防火墙
enabled: true
config:
multi-statement-allow: true # 允许多条 SQL
none-base-statement-allow: true
slf4j: # 日志
enabled: true
statement-create-after-log-enabled: false
statement-close-after-log-enabled: false
result-set-open-after-log-enabled: false
result-set-close-after-log-enabled: false
4.3 Java 配置方式
/**
* Druid Java 配置
*/
@Configuration
public class DruidConfig {
/**
* Druid 数据源配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource druidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
// 基本配置
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 连接池配置
dataSource.setInitialSize(10);
dataSource.setMinIdle(10);
dataSource.setMaxActive(50);
dataSource.setMaxWait(60000);
// 连接验证
dataSource.setValidationQuery("SELECT 1");
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(false);
dataSource.setTestOnReturn(false);
// 空闲连接检测
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
// PreparedStatement 缓存
dataSource.setPoolPreparedStatements(true);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
// 启用监控过滤器
try {
dataSource.setFilters("stat,wall,slf4j");
} catch (SQLException e) {
throw new RuntimeException(e);
}
return dataSource;
}
/**
* Druid 监控页面配置
*/
@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 监控页面访问权限
registrationBean.addInitParameter("loginUsername", "admin");
registrationBean.addInitParameter("loginPassword", "admin");
registrationBean.addInitParameter("resetEnable", "false");
return registrationBean;
}
/**
* Druid Web 监控过滤器
*/
@Bean
public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {
FilterRegistrationBean<WebStatFilter> registrationBean =
new FilterRegistrationBean<>(new WebStatFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter("exclusions",
"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return registrationBean;
}
}
/**
* Druid 多数据源配置
*/
@Configuration
public class MultiDruidConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary.druid")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary.druid")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
}
4.4 Druid 监控功能
/**
* Druid 监控功能详解
*/
public class DruidMonitorFeatures {
/**
* 访问监控页面
* http://localhost:8080/druid/
*
* 功能:
* 1. 数据源信息
* 2. SQL 监控
* 3. SQL 防火墙
* 4. Web 应用监控
* 5. URI 监控
* 6. Spring 监控
*/
/**
* SQL 监控统计信息
*/
@Service
public class DruidStatService {
@Autowired
private DruidDataSource druidDataSource;
/**
* 获取 SQL 统计信息
*/
public List<SqlStat> getSqlStats() {
List<SqlStat> stats = new ArrayList<>();
Map<Long, JdbcSqlStat> sqlStatMap =
druidDataSource.getDataSourceStat().getSqlStatMap();
for (JdbcSqlStat stat : sqlStatMap.values()) {
SqlStat sqlStat = new SqlStat();
sqlStat.setSql(stat.getSql());
sqlStat.setExecuteCount(stat.getExecuteCount());
sqlStat.setTotalTime(stat.getTotalTimeNano());
sqlStat.setMaxTimespan(stat.getMaxTimespan());
sqlStat.setEffectedRowCount(stat.getEffectedRowCount());
sqlStat.setFetchRowCount(stat.getFetchRowCount());
sqlStat.setConcurrentMax(stat.getConcurrentMax());
sqlStat.setRunningCount(stat.getRunningCount());
stats.add(sqlStat);
}
return stats;
}
/**
* 获取数据源状态
*/
public DataSourceStat getDataSourceStat() {
DataSourceStat stat = new DataSourceStat();
DruidDataSourceStatValue statValue =
druidDataSource.getDataSourceStat().cloneStatValue();
stat.setActiveCount(statValue.getActiveCount());
stat.setPoolingCount(statValue.getPoolingCount());
stat.setMaxActive(statValue.getMaxActive());
stat.setWaitThreadCount(statValue.getWaitThreadCount());
stat.setActiveCountPeak(statValue.getActivePeak());
return stat;
}
}
/**
* SQL 统计 DTO
*/
@Data
public static class SqlStat {
private String sql;
private long executeCount;
private long totalTime;
private long maxTimespan;
private long effectedRowCount;
private long fetchRowCount;
private int concurrentMax;
private int runningCount;
}
/**
* 数据源状态 DTO
*/
@Data
public static class DataSourceStat {
private int activeCount;
private int poolingCount;
private int maxActive;
private int waitThreadCount;
private int activeCountPeak;
}
}
/**
* Druid Spring 监控配置
*/
@Configuration
public class DruidSpringMonitorConfig {
/**
* Spring 方法监控
*/
@Bean
public DruidStatInterceptor druidStatInterceptor() {
return new DruidStatInterceptor();
}
@Bean
@Scope("prototype")
public JdkRegexpMethodPointcut druidStatPointcut() {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPatterns(
"com.example.service.*",
"com.example.mapper.*"
);
return pointcut;
}
@Bean
public Advisor druidStatAdvisor() {
return new DefaultPointcutAdvisor(druidStatPointcut(), druidStatInterceptor());
}
}
五、连接池对比与选择
5.1 主流连接池对比
/**
* 主流连接池对比
*/
public class ConnectionPoolComparison {
/*
└─────────────────────────────────────────────────────────────────────────┘
/**
* SQL 防火墙配置
*/
@Configuration
public class DruidWallConfig {
@Bean
public WallFilter wallFilter() {
WallFilter filter = new WallFilter();
filter.setConfig(wallConfig());
return filter;
}
@Bean
public WallConfig wallConfig() {
WallConfig config = new WallConfig();
// 允许的 SQL 操作
config.setSelectAllow(true);
config.setInsertAllow(true);
config.setUpdateAllow(true);
config.setDeleteAllow(true);
// 禁止的危险操作
config.setDropTableAllow(false); // 禁止删除表
config.setAlterTableAllow(false); // 禁止修改表结构
config.setTruncateAllow(false); // 禁止截断表
config.setCreateTableAllow(false); // 禁止创建表
// 多条 SQL 执行
config.setMultiStatementAllow(true);
return config;
}
}
五、连接池调优
5.1 参数调优指南
/**
* 连接池参数调优
*/
public class ConnectionPoolTuning {
/**
* 连接池大小计算
*/
public void calculatePoolSize() {
// PostgreSQL 官方公式:
// connections = (core_count * 2) + effective_spindle_count
// 但实际需要考虑:
// 1. 业务类型(IO 密集型 vs CPU 密集型)
// 2. 数据库服务器配置
// 3. 并发用户数
// 4. 平均查询响应时间
// 推荐步骤:
// 1. 使用公式计算初始值
// 2. 压测验证
// 3. 监控调整
}
/**
* 超时时间设置原则
*/
// connectionTimeout:
// - 应小于业务超时时间
// - 通常设置 30 秒
// - 高并发可适当增大
// idleTimeout:
// - 大于连接检测间隔
// - 通常设置 10 分钟
// - 流量稳定时可增大
// maxLifetime:
// - 必须小于数据库 wait_timeout
// - MySQL 默认 8 小时
// - 建议 30 分钟
// - 考虑数据库重启、网络中断等情况
/**
* 连接验证配置
*/
// 推荐使用 JDBC4 驱动,不需要设置 connectionTestQuery
// testWhileIdle: true(推荐)
// testOnBorrow: false(影响性能)
// testOnReturn: false(影响性能)
}
/**
* 连接池监控与告警
*/
@Configuration
public class ConnectionPoolAlertConfig {
/**
* 连接池健康检查
*/
@Service
public class ConnectionPoolHealthService {
@Autowired
private DataSource dataSource;
@Scheduled(fixedRate = 10000)
public void checkPoolHealth() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
if (pool != null) {
int active = pool.getActiveConnections();
int total = pool.getTotalConnections();
int waiting = pool.getThreadsAwaitingConnection();
// 计算连接使用率
double usageRate = (double) active / total;
// 告警阈值
if (usageRate > 0.8) {
log.warn("连接池使用率过高: {}%, 活跃: {}, 等待: {}",
String.format("%.2f", usageRate * 100), active, waiting);
}
if (waiting > 5) {
log.warn("等待连接的线程过多: {}", waiting);
}
}
}
}
/**
* 获取连接池指标
*/
public Map<String, Object> getPoolMetrics() {
Map<String, Object> metrics = new HashMap<>();
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
if (pool != null) {
metrics.put("activeConnections", pool.getActiveConnections());
metrics.put("idleConnections", pool.getIdleConnections());
metrics.put("totalConnections", pool.getTotalConnections());
metrics.put("threadsAwaitingConnection",
pool.getThreadsAwaitingConnection());
}
}
return metrics;
}
}
}
5.2 常见问题排查
/**
* 连接池常见问题排查
*/
@Service
public class ConnectionPoolTroubleshooting {
/**
* 问题1:连接泄露
*/
// 现象:
// - 连接池连接数持续增长
// - 最终无法获取连接
// - 日志显示连接泄露警告
// 原因:
// - 获取连接后未关闭
// - try-with-resources 使用不当
// - 连接被意外持有
// 排查:
// 1. 开启泄露检测
dataSource.setLeakDetectionThreshold(60000);
// 2. 检查代码中的 Connection 使用
public void correctUsage() {
// 正确用法:使用 try-with-resources
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT 1")) {
// 使用连接...
} // 自动关闭
}
public void wrongUsage() {
// 错误用法:忘记关闭
Connection conn = dataSource.getConnection();
// 使用连接后未关闭
}
/**
* 问题2:连接超时
*/
// 现象:
// - 获取连接时报超时异常
// - HikariPool-1 - Connection is not available
// 原因:
// - 连接池太小
// - 连接持有时间过长
// - 慢 SQL 导致连接占用
// 排查:
// 1. 检查连接池大小
// 2. 监控活跃连接数
// 3. 排查慢 SQL
// 4. 检查是否有事务未提交
/**
* 问题3:连接失效
*/
// 现象:
// - 执行 SQL 时报连接已关闭
// - MySQL has gone away
// 原因:
// - 数据库重启
// - 连接超过 maxLifetime
// - 网络中断
// - 数据库 wait_timeout 过小
// 解决:
// 1. 设置合理的 maxLifetime
dataSource.setMaxLifetime(1800000); // 30 分钟
// 2. 开启连接验证
dataSource.setConnectionTestQuery("SELECT 1");
// 3. Druid 设置 keep-alive
druidDataSource.setKeepAlive(true);
/**
* 问题4:连接池初始化失败
*/
// 现象:
// - 启动时报错无法创建连接
// 排查:
// 1. 检查数据库连接配置
// 2. 检查网络连通性
// 3. 检查数据库用户权限
// 4. 检查数据库连接数限制
/**
* 连接池诊断工具
*/
public void diagnoseConnectionPool() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
System.out.println("连接池配置:");
System.out.println("poolName: " + hikari.getPoolName());
System.out.println("maximumPoolSize: " + hikari.getMaximumPoolSize());
System.out.println("minimumIdle: " + hikari.getMinimumIdle());
System.out.println("connectionTimeout: " + hikari.getConnectionTimeout());
System.out.println("idleTimeout: " + hikari.getIdleTimeout());
System.out.println("maxLifetime: " + hikari.getMaxLifetime());
HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
if (pool != null) {
System.out.println("\n连接池状态:");
System.out.println("activeConnections: " + pool.getActiveConnections());
System.out.println("idleConnections: " + pool.getIdleConnections());
System.out.println("totalConnections: " + pool.getTotalConnections());
System.out.println("threadsAwaiting: " +
pool.getThreadsAwaitingConnection());
}
}
}
}
六、HikariCP vs Druid 对比
┌─────────────────────────────────────────────────────────────────────────┐
│ HikariCP vs Druid 对比 │
├──────────────────┬──────────────────────┬───────────────────────────────┤
│ 特性 │ HikariCP │ Druid │
├──────────────────┼──────────────────────┼───────────────────────────────┤
│ 性能 │ 极高 │ 高 │
│ 代码量 │ ~130KB │ ~2MB │
│ 监控功能 │ 基础(需集成 Micrometer)│ 强大(内置监控页面) │
│ SQL 监控 │ 无 │ 有 │
│ SQL 防火墙 │ 无 │ 有 │
│ 配置复杂度 │ 简单 │ 较复杂 │
│ Spring Boot 集成 │ 默认 │ 需额外依赖 │
│ 社区活跃度 │ 高 │ 中 │
│ 适用场景 │ 追求性能、简单场景 │ 需要监控、安全防护 │
└──────────────────┴──────────────────────┴───────────────────────────────┘
七、最佳实践
7.1 配置最佳实践
# 推荐的 HikariCP 生产配置
spring:
datasource:
hikari:
pool-name: ${spring.application.name}-pool
# 连接池大小(根据实际压测调整)
minimum-idle: 10
maximum-pool-size: 50
# 超时配置
connection-timeout: 30000 # 30秒
idle-timeout: 600000 # 10分钟
max-lifetime: 1800000 # 30分钟(小于 MySQL wait_timeout)
# 验证配置(使用 JDBC4 驱动无需设置 connection-test-query)
validation-timeout: 5000
# 泄露检测(生产环境可设置较大值或关闭)
leak-detection-threshold: 0
# MySQL 优化
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
# 推荐的 Druid 生产配置
spring:
datasource:
druid:
initial-size: 10
min-idle: 10
max-active: 50
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
keep-alive: true
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 3000
wall:
enabled: true
7.2 监控最佳实践
/**
* 连接池监控最佳实践
*/
@Configuration
public class PoolMonitorBestPractice {
// 1. 开启 JMX 监控
// 2. 集成 Micrometer 指标
// 3. 定时打印连接池状态
// 4. 设置告警阈值
// 5. 记录慢 SQL
/**
* 关键监控指标
*/
// - activeConnections: 活跃连接数
// - idleConnections: 空闲连接数
// - totalConnections: 总连接数
// - threadsAwaitingConnection: 等待连接的线程数
// - connectionCreationTime: 连接创建时间
// - connectionAcquireTime: 获取连接时间
// - connectionUsageTime: 连接使用时间
}
参考资料:
- HikariCP GitHub:github.com/brettwooldr…
- Druid GitHub:github.com/alibaba/dru…
- 《高性能数据库连接池优化》
八、思考与练习
思考题
-
基础题:为什么需要连接池?连接池相比直接创建连接有哪些优势?
-
进阶题:HikariCP 为什么性能极高?它做了哪些优化?Druid 的核心优势是什么?
-
实战题:在高并发场景下,如何诊断连接池配置是否合理?出现连接超时时应该如何排查和解决?
编程练习
练习:为一个高并发电商系统配置和优化数据库连接池,要求:
- 使用 HikariCP 配置连接池,合理设置 minimum-idle、maximum-pool-size、connection-timeout 等参数
- 编写监控代码,定期输出连接池状态(活跃连接数、空闲连接数、等待线程数)
- 实现连接泄露检测功能,当连接持有时间超过阈值时打印警告日志
- 使用 JMeter 进行压力测试,观察不同连接池配置对系统性能的影响
- 记录并分析慢 SQL,找出导致连接占用时间过长的原因
章节关联
- 前置章节:多数据源配置详解
- 后续章节:Spring 事务管理进阶
- 扩展阅读:
- HikariCP 源码分析
- 数据库性能调优实战
- 高并发系统设计方法论
📝 下一章预告
至此,Spring 生态中的数据访问技术已经系统讲解完毕。下一章节将进入事务管理专题,深入讲解 Spring 事务的传播机制、隔离级别、失效场景以及分布式事务解决方案,帮助你构建数据一致性有保障的企业级应用。
本章完