38-数据库连接池详解

1 阅读16分钟

数据库连接池详解

本章导读

数据库连接池是 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: 连接使用时间
}

参考资料:


八、思考与练习

思考题

  1. 基础题:为什么需要连接池?连接池相比直接创建连接有哪些优势?

  2. 进阶题:HikariCP 为什么性能极高?它做了哪些优化?Druid 的核心优势是什么?

  3. 实战题:在高并发场景下,如何诊断连接池配置是否合理?出现连接超时时应该如何排查和解决?

编程练习

练习:为一个高并发电商系统配置和优化数据库连接池,要求:

  1. 使用 HikariCP 配置连接池,合理设置 minimum-idle、maximum-pool-size、connection-timeout 等参数
  2. 编写监控代码,定期输出连接池状态(活跃连接数、空闲连接数、等待线程数)
  3. 实现连接泄露检测功能,当连接持有时间超过阈值时打印警告日志
  4. 使用 JMeter 进行压力测试,观察不同连接池配置对系统性能的影响
  5. 记录并分析慢 SQL,找出导致连接占用时间过长的原因

章节关联

  • 前置章节:多数据源配置详解
  • 后续章节:Spring 事务管理进阶
  • 扩展阅读
    • HikariCP 源码分析
    • 数据库性能调优实战
    • 高并发系统设计方法论

📝 下一章预告

至此,Spring 生态中的数据访问技术已经系统讲解完毕。下一章节将进入事务管理专题,深入讲解 Spring 事务的传播机制、隔离级别、失效场景以及分布式事务解决方案,帮助你构建数据一致性有保障的企业级应用。


本章完