用了这么久 MySQL 连接池,终于知道为什么要调这几个参数

用了这么久 MySQL 连接池,终于知道为什么要调这几个参数

应用启动时报了个错:

Caused by: java.sql.SQLException: Cannot get connection for URL jdbc:mysql://localhost:3306/ads
: Could not create C3P0 connection pool: An attempt by a client to checkout a Connection has timed out

看了眼监控,MySQL 连接数已经打满了。

上去一看:

SHOW STATUS LIKE 'Threads_connected';
-- 151 / 200

DBA 打电话过来:你们连接数太高了,限制的是 200。

问题在哪?连接池参数配置不合理。

MySQL 连接的本质

每一条 MySQL 连接,服务器端都会创建:

  • 一个线程处理请求
  • 一些内存 buffer
  • 握手认证、字符集设置等开销

MySQL 的 max_connections 参数控制最大连接数:

SHOW VARIABLES LIKE 'max_connections';
-- 200(默认)

连接数打满后的后果:

  • 新请求无法建立连接,报错
  • 服务器端要管理大量线程,上下文切换开销大
  • 内存占用飙升

常见连接池对比

连接池最小连接最大连接特性
Druid可配置可配置阿里开源,监控强
HikariCP1020性能最强,Spring Boot 2.x 默认
C3P0315老牌,懒加载
DBCP520Apache 遗产

我们用的 HikariCP,Spring Boot 2.x 默认。

核心参数详解

1. maximumPoolSize / maxPoolSize

最大连接数,这是最关键的参数。

设太小:并发请求排队等连接,P99 延迟飙升 设太大:MySQL 连接数打满,上下文切换开销大

经验公式:

maximumPoolSize = (核心线程数 * CPU利用率) / 单个连接占用CPU比例

更简单的做法:

  • 纯 CPU 计算型:CPU 核数 * 2
  • IO 密集型(数据库访问):CPU 核数 / (1 - 阻塞系数)

假设 8 核 CPU,阻塞系数 0.8:

8 / (1 - 0.8) = 40

但还要看 MySQL 的 max_connections

SHOW VARIABLES LIKE 'max_connections';
-- 假设是 200

MySQL 官方建议 max_connections = 100 足够一般使用,200 足够中等规模。

DBA 一般会限制每个应用的连接数上限,比如 20-50。

所以 maximumPoolSize 要跟 DBA 确认,不能单方面设太大。

2. minimumIdle / minPoolSize

最小空闲连接数。

设太小:低峰期频繁创建销毁连接,开销大 设太大:长期占用连接资源,浪费

HikariCP 建议:

  • 如果连接经常使用,设大一些保持连接活跃
  • 如果是间歇性使用,可以让连接池自己回收

HikariCP 默认 minimumIdle = maximumPoolSize,即始终保持最大连接数。如果你想省资源,可以设小一些:

spring:
  datasource:
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20

3. connectionTimeout

获取连接的超时时间,默认 30 秒。

这个值要设,但不能太大。太大意味着请求会长时间阻塞,太小会频繁报连接超时。

计算公式:

connectionTimeout >= 正常SQL耗时 * (1 + 并发倍数) + 网络延迟

假设:

  • 正常 SQL 耗时:50ms
  • 峰值并发倍数:3x
  • 网络延迟:10ms
50 * 4 + 10 = 210ms

设 30 秒太浪费,设 500ms 可能不够,考虑 5-10 秒。

4. idleTimeout / minEvictableIdleTimeMillis

连接空闲时间超过这个值,会被回收。

HikariCP 默认 10 分钟。

注意:这个参数只有 minimumIdle < maximumPoolSize 时才生效。

场景:

  • 连接池不常用,希望释放空闲连接节省资源
  • 数据库有连接超时限制(MySQL 默认 8 小时 wait_timeout

如果你的应用持续高负载,这个参数影响不大。如果间歇性使用,设短一些可以及时释放资源。

5. maxLifetime

连接的最大生命周期,超过后会被销毁重建。

为什么要设这个?

  • MySQL 服务端有 wait_timeout(默认 8 小时)
  • 长时间空闲的连接可能被服务端关闭
  • 避免连接被复用时出现问题

建议比 MySQL 的 wait_timeout 短一些:

SHOW VARIABLES LIKE 'wait_timeout';
-- 28800 秒(8小时)

-- 设置连接池
spring:
  datasource:
    hikari:
      max-lifetime: 1800  # 30分钟,提前回收

6. connectionTestQuery

获取连接后、执行前执行的测试查询。

HikariCP 默认用 ping 检测连接有效性,性能最好。如果你的驱动不支持 ping,手动指定:

spring:
  datasource:
    hikari:
      connection-test-query: SELECT 1

但更推荐 HikariCP 的默认行为,不显式配置这个参数。

7. leakDetectionThreshold

连接泄漏检测阈值。

连接泄漏:获取连接后没有关闭,导致连接一直被占用。

设置为大于 0 启用检测:

spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000  # 60秒未归还视为泄漏

注意:开启检测会影响性能,生产环境可以设长一些(60 秒),开发测试环境可以设短一些(10 秒)帮助发现问题。

一个完整的配置示例

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ads?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      # 连接池大小
      minimum-idle: 5
      maximum-pool-size: 20
      # 超时时间
      connection-timeout: 10000
      idle-timeout: 300000
      max-lifetime: 1800000
      # 连接测试
      connection-test-query: SELECT 1
      # 泄漏检测
      leak-detection-threshold: 60000
      # 性能优化
      auto-commit: true
      pool-name: HikariCP-ads

连接池监控

连接池自带监控接口,可以暴露到 Prometheus:

@Configuration
public class HikariMetricsConfig {
    @Bean
    public MeterRegistry meterRegistry() {
        return new MeterRegistry();
    }

    @PostConstruct
    public void init() {
        HikariConfigMXBean beans = hikariDataSource.getHikariPoolMXBean();
        // 暴露 metrics
    }
}

关键监控指标:

  • HikariPool-PoolStarting — 连接池启动中
  • HikariPool-PoolConnections — 当前连接数
  • HikariPool-IdleConnections — 空闲连接数
  • HikariPool-ActiveConnections —活跃连接数
  • HikariPool-PendingConnections — 等待获取连接的线程数
  • HikariPool-Timeouts — 连接超时次数

报警策略:

  • PendingConnections > 0 持续 30 秒 — 连接池不够用
  • Timeouts 速率 > 0.1/s — 超时频繁
  • ActiveConnections == maximumPoolSize 持续 5 分钟 — 连接池满载

常见问题排查

连接数打满

-- 查看各应用的连接数分布
SELECT
    SUBSTRING_INDEX(host, ':', 1) AS client_ip,
    COUNT(*) AS connection_count
FROM information_schema.processlist
GROUP BY client_ip
ORDER BY connection_count DESC;

-- 查看当前连接
SHOW PROCESSLIST;

连接等待超时

java.sql.SQLTimeoutException: connection pool exhausted

调整 connectionTimeout 或增加 maximumPoolSize(联系 DBA 先扩容 MySQL 连接数)。

连接泄漏

java.lang.Exception: Apparent connection leak detected

在代码里找有没有 Connection 用完没 close() 的地方。用 try-with-resources 包裹:

// 错误写法
Connection conn = dataSource.getConnection();
try {
    // do something
} finally {
    // 容易忘记 close
    // conn.close();
}

// 正确写法
try (Connection conn = dataSource.getConnection()) {
    // do something
}  // 自动关闭

DNS 反向查询导致连接慢

MySQL 默认做 DNS 反向解析,DNS 服务慢会影响连接速度。关闭:

SHOW VARIABLES LIKE 'skip_name_resolve';
-- OFF 表示未开启

-- 临时开启
SET GLOBAL skip_name_resolve = ON;

-- 永久生效需要在 my.cnf 配置
[mysqld]
skip-name-resolve

客户端连接时用 IP 而不是主机名。

连接数规划的完整流程

  1. 压测摸底:单实例最大 QPS 下,连接池应该多大
  2. DBA 协调:MySQL max_connections 上限是多少,单应用分配多少
  3. 配置连接池maximumPoolSize = 单应用分配上限 * 1.2(留 buffer)
  4. 监控调优:观察 PendingConnectionsTimeouts,动态调整
  5. 定期巡检:连接池利用率是否合理,有无泄漏

总结

连接池参数配置不是玄学,关键理解几个核心概念:

  • maximumPoolSize:不要超过 MySQL 允许的单应用连接数
  • minimumIdle:持续负载可以等于最大池大小,间歇性负载可以设小
  • connectionTimeout:正常 SQL 耗时的 5-10 倍
  • maxLifetime:比 MySQL wait_timeout
  • leakDetectionThreshold:开启监控,帮忙发现连接泄漏

连接池调优的目标是:连接够用、不浪费、不泄漏。在此基础上配合慢查询优化和缓存,数据库性能可以提升一个数量级。

最后更新 4/20/2026, 6:02:32 AM