知识模块
☕ Java 知识模块
七、数据库与JDBC
HikariCP 连接池

HikariCP 连接池

为什么需要连接池?

没有连接池的问题

// 每次请求都创建和关闭连接
public User getUser(int id) {
    Connection conn = DriverManager.getConnection(url, user, password); // 耗时操作
    // ... 执行查询
    conn.close(); // 耗时操作
}

问题:

  1. 创建连接开销大:TCP 握手、SSL 认证、权限验证
  2. 连接数不可控:并发高峰可能耗尽数据库连接
  3. 资源浪费:频繁创建销毁连接

连接池的作用

应用程序                    连接池                     数据库
    │                         │                         │
    │── 请求连接 ─────────────→│                         │
    │                         │── 复用已建立连接 ───────→│
    │←──────── 返回连接 ──────│                         │
    │                         │                         │
    │── 归还连接 ─────────────→│←── 连接保持 ───────────│
    │                         │                         │

优势:

  • 复用连接,减少创建开销
  • 控制最大连接数,保护数据库
  • 提供连接健康检查

HikariCP 简介

为什么 HikariCP 最快?

优化点说明
字节码级别优化使用 Javassist 生成代理类
ConcurrentBag自定义并发容器,减少锁竞争
FastList优化的 ArrayList,减少范围检查
代理优化精简的 Connection 代理实现

性能对比

连接池获取连接耗时吞吐量
HikariCP~0.1ms最高
Druid~0.5ms
C3P0~2ms
DBCP~3ms

核心配置参数

基础配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      # 连接池名称
      pool-name: MyHikariPool
      
      # 最小空闲连接数
      minimum-idle: 5
      
      # 最大连接数
      maximum-pool-size: 20
      
      # 连接超时时间(毫秒)
      connection-timeout: 30000
      
      # 空闲连接存活最大时间(毫秒)
      idle-timeout: 600000
      
      # 连接最大存活时间(毫秒)
      max-lifetime: 1800000
      
      # 连接测试查询
      connection-test-query: SELECT 1

参数详解

参数默认值说明
maximum-pool-size10最大连接数
minimum-idle同 maximum最小空闲连接数
connection-timeout30s等待连接的超时时间
idle-timeout10min空闲连接最大存活时间
max-lifetime30min连接最大存活时间
connection-test-query连接有效性测试 SQL
leak-detection-threshold0连接泄露检测阈值

最大连接数计算公式

最大连接数 = (核心数 * 2) + 有效磁盘数

例如:8核CPU + 1块磁盘 = (8 * 2) + 1 = 17

原因:

  • 计算密集型任务:核心数 + 1
  • I/O 密集型任务:核心数 * 2
  • 数据库操作是 I/O 密集型

实际配置: 根据业务压力测试调整,不是越大越好。

连接池工作原理

连接获取流程

应用程序请求连接

检查空闲连接列表

有可用连接? → 是 → 验证连接有效性 → 返回连接
       ↓ 否
当前连接数 < 最大值?
       ↓ 是              ↓ 否
创建新连接          等待其他连接释放
       ↓                    ↓
返回连接           超时? → 抛异常

连接归还流程

应用程序归还连接

重置连接状态(回滚事务、清除警告等)

当前空闲连接数 < 最小空闲数?
       ↓ 是            ↓ 否
放入空闲池       关闭连接

连接泄露检测

问题场景

// 连接泄露:忘记关闭连接
public void leak() {
    Connection conn = dataSource.getConnection();
    // 业务逻辑...
    // 忘记 conn.close()
}

检测配置

spring:
  datasource:
    hikari:
      # 连接泄露检测阈值(毫秒)
      # 连接借出超过此时间未归还,记录警告日志
      leak-detection-threshold: 60000  # 60秒

日志输出

警告 - Connection leak detection triggered for connection xxx
Connection has been out of pool for 60001ms

常见问题

1. ConnectionTimeoutException

Caused by: java.sql.SQLTransientConnectionException: 
HikariPool - Connection is not available, request timed out after 30000ms

原因:

  • 连接池太小
  • 连接泄露
  • 查询太慢

解决:

# 增大连接池
maximum-pool-size: 30
 
# 增加超时时间
connection-timeout: 60000
 
# 检查泄露
leak-detection-threshold: 30000

2. 连接池预热

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        // ... 配置参数
        
        // 预热连接池
        ds.getConnection().close(); // 触发初始化
        
        return ds;
    }
}

3. MySQL 连接断开

MySQL 默认 wait_timeout=8小时,连接空闲超过 8 小时会断开。

解决:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?autoReconnect=true
    hikari:
      # 连接最大存活时间小于 MySQL wait_timeout
      max-lifetime: 1800000  # 30分钟
      
      # 定期测试连接
      connection-test-query: SELECT 1
      
      # 或使用 MySQL 驱动的自动重连
      # autoReconnect=true(已废弃,不推荐)

监控指标

JMX 监控

spring:
  datasource:
    hikari:
      register-mbeans: true

使用 JConsole 或 VisualVM 连接查看:

  • totalConnections:总连接数
  • activeConnections:活跃连接数
  • idleConnections:空闲连接数
  • threadsAwaitingConnection:等待连接的线程数

Prometheus + Micrometer

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
// 暴露 HikariCP 指标
@Bean
public MeterBinder hikariMetrics(HikariDataSource dataSource) {
    return new HikariDataSourceMetrics(dataSource, "my-pool", Collections.emptyList());
}

关键指标:

  • hikaricp_connections_active:活跃连接数
  • hikaricp_connections_idle:空闲连接数
  • hikaricp_connections_pending:等待获取连接的线程数
  • hikaricp_connections_creation_seconds:连接创建耗时

与其他连接池对比

特性HikariCPDruidC3P0
性能最高
监控基础丰富(内置监控页面)基础
SQL 监控
配置复杂度简单中等复杂
Spring Boot 默认

选择建议:

  • HikariCP:追求性能,Spring Boot 默认
  • Druid:需要 SQL 监控、防注入等功能

最佳实践配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: ${DB_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      pool-name: MyAppPool
      
      # 根据实际负载调整
      maximum-pool-size: 20
      minimum-idle: 5
      
      # 超时配置
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      
      # 泄露检测(生产环境建议开启)
      leak-detection-threshold: 60000
      
      # 连接验证
      validation-timeout: 5000

面试高频问题

Q1: HikariCP 为什么比其他连接池快?

  1. 字节码优化:Javassist 生成代理类,减少反射调用
  2. ConcurrentBag:自定义并发容器,无锁设计
  3. 精简实现:代码量少,减少不必要的功能
  4. FastList:优化的集合,减少边界检查

Q2: 连接池最大连接数如何设置?

公式:(CPU核心数 * 2) + 磁盘数

实际:需要结合业务压测调整
- 太大:数据库压力大,上下文切换开销
- 太小:并发请求排队等待

Q3: 连接泄露如何排查?

  1. 开启泄露检测:leak-detection-threshold
  2. 查看日志定位泄露代码
  3. 检查未关闭连接的代码
  4. 使用 try-with-resources 确保关闭

Q4: HikariCP 和 Druid 如何选择?

场景推荐
追求性能HikariCP
需要 SQL 监控Druid
Spring Boot 项目HikariCP(默认)
防止 SQL 注入Druid(内置 wall filter)

总结

HikariCP 核心要点:
1. 性能最优:字节码优化 + ConcurrentBag
2. 关键配置:maximum-pool-size、connection-timeout
3. 泄露检测:leak-detection-threshold
4. 连接数公式:(核心数 * 2) + 磁盘数
5. 监控指标:活跃连接、空闲连接、等待线程