你知道MySQL连接数过小会导致数据库查询变慢吗?

摘要:从一次"应用并发上来后数据库响应变慢10倍"的性能故障出发,深度剖析MySQL连接池配置不当导致的性能问题。通过连接池排队、连接耗尽、以及连接创建开销的真实案例,揭秘为什么max_connections设置过小会成为瓶颈、HikariCP连接池的最佳实践、以及如何监控连接池状态。配合时序图展示连接等待流程,给出不同并发场景下的连接数计算公式。


💥 翻车现场

周二下午,哈吉米的应用上线了。

压测结果(单用户)

接口:/api/order/create
响应时间:50ms ✅
数据库查询时间:5ms

压测结果(100并发)

接口响应时间:500ms ❌(慢了10倍)
数据库查询时间:5ms(没变)

问题:数据库查询时间没变,为什么接口变慢了?

哈吉米:"数据库查询才5ms,怎么接口要500ms?"

查看监控:

应用监控:
- 活跃线程:100
- 数据库连接池等待时间:495ms ← 问题在这

数据库监控:
- 当前连接数:10
- 查询响应时间:5ms

哈吉米:"连接池等待了495ms?为什么?"

查看配置:

# application.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 10  # 最大连接数:10
      minimum-idle: 5

问题分析

场景:
1. 100个请求并发进来
2. 每个请求需要1个数据库连接
3. 连接池只有10个连接
4. 前10个请求立即获取连接,执行查询(5ms)
5. 后90个请求等待连接释放(排队)

时间线:
T0: 100个请求进来
T1: 前10个请求获取连接,开始查询
T5: 前10个请求查询完成,释放连接
T6: 后10个请求获取连接,开始查询
T11: 后10个请求查询完成,释放连接
...
T495: 最后10个请求查询完成

平均等待时间:495ms / 2 ≈ 250ms
平均响应时间:250ms(等待) + 5ms(查询) = 255ms

哈吉米:"卧槽,连接池太小了,大家在排队等连接!"

南北绿豆和阿西噶阿西来了。

南北绿豆:"连接池是性能的关键,配置不当会导致严重的性能问题!"
阿西噶阿西:"来,我给你讲讲连接数的配置原理。"


🤔 为什么连接数过小会慢?

连接池的工作原理

阿西噶阿西在白板上画了一个图。

连接池(最大10个连接):

┌─────────────────────────────┐
│  连接池(HikariCP)          │
│                             │
│  [conn1] [conn2] [conn3]    │
│  [conn4] [conn5] [conn6]    │
│  [conn7] [conn8] [conn9]    │
│  [conn10]                   │
│                             │
│  等待队列:                  │
│  [请求11] [请求12] [请求13]  │
│  [请求14] ... [请求100]     │  ← 90个请求在等待
└─────────────────────────────┘

连接等待的时序图

sequenceDiagram
    participant Req1 as 请求1
    participant Req11 as 请求11
    participant Pool as 连接池(10个连接)
    participant DB as MySQL

    par 前10个请求立即获取连接
        Req1->>Pool: 1. 获取连接
        Pool->>Req1: 2. 返回conn1 ✅
        Req1->>DB: 3. 查询(5ms)
        DB->>Req1: 4. 返回数据
        Req1->>Pool: 5. 归还conn1
    end
    
    Req11->>Pool: 6. 获取连接(T0时刻)
    Note over Pool: 连接池已满,等待
    Note over Req11: 阻塞等待连接释放...
    
    Note over Req1: 5ms后释放连接
    Pool->>Req11: 7. 返回conn1(T5时刻)
    Note over Req11: 等待时间:5ms
    
    Req11->>DB: 8. 查询(5ms)
    DB->>Req11: 9. 返回数据
    
    Note over Req11: 总耗时:5ms(等待) + 5ms(查询) = 10ms

南北绿豆:"看到了吗?连接数不够,请求会排队等待连接,导致响应时间暴增。"


🎯 连接数应该设置多少?

计算公式

阿西噶阿西:"连接数的设置有公式。"

公式:
连接数 = 并发请求数 × 单次查询时间 / 查询间隔

简化公式(QPS维度):
连接数 = 峰值QPS × 平均查询时间

示例:
峰值QPS:1000
平均查询时间:0.01秒(10ms)

连接数 = 1000 × 0.01 = 10

但要考虑峰值和突发:
推荐连接数 = 10 × 2 = 20

HikariCP官方推荐

HikariCP作者的建议:
连接数 = ((核心数 × 2) + 磁盘数)

示例:
CPU:8核
磁盘:1个SSD

连接数 = (8 × 2) + 1 = 17

理由:
- CPU密集:核心数即可
- IO密集(数据库查询):核心数 × 2
- 磁盘IO:+1

不同场景的推荐值

场景QPS平均查询时间推荐连接数
低并发10010ms10-20
中并发100010ms20-50
高并发1000010ms100-200
超高并发10000010ms1000+

注意

连接数不是越多越好:
1. MySQL有最大连接数限制(max_connections)
2. 每个连接占用内存(约256KB)
3. 连接过多,MySQL调度开销大

推荐:
- 应用连接池:20-100
- MySQL max_connections:500-1000

🎯 MySQL max_connections的设置

查看和设置

-- 查看当前最大连接数
SHOW VARIABLES LIKE 'max_connections';

+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| max_connections  | 151   |  ← 默认151
+------------------+-------+

-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';

+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 125   |  ← 当前125个连接
+-------------------+-------+

-- 临时设置(重启后失效)
SET GLOBAL max_connections = 500;

-- 永久设置(my.cnf)
[mysqld]
max_connections = 500

max_connections设置不当的问题

场景1:设置过小

max_connections = 100

10台应用服务器:
每台连接池:20个连接
总连接数:10 × 20 = 200

问题:
200 > 100(max_connections)
→ 部分应用连接不上
→ ERROR 1040: Too many connections ❌

场景2:设置过大

max_connections = 10000

问题:
1. 每个连接占用内存:256KB
2. 总内存:10000 × 256KB = 2.5GB
3. 连接过多,MySQL调度开销大
4. 性能反而下降 ❌

推荐配置

应用数量 × 每个应用的连接池大小 < MySQL max_connections × 0.8

示例:
10台应用服务器
每台连接池:50
MySQL max_connections = 10 × 50 / 0.8 = 625

推荐设置:
max_connections = 1000(留余量)

🎯 HikariCP连接池配置

核心参数

spring:
  datasource:
    hikari:
      # 最大连接数
      maximum-pool-size: 50
      
      # 最小空闲连接数
      minimum-idle: 10
      
      # 连接超时时间(获取连接的超时)
      connection-timeout: 30000  # 30秒
      
      # 空闲连接存活时间
      idle-timeout: 600000  # 10分钟
      
      # 连接最大存活时间
      max-lifetime: 1800000  # 30分钟
      
      # 连接测试查询(验证连接是否有效)
      connection-test-query: SELECT 1

参数详解

maximum-pool-size(最大连接数)

作用:连接池最多能创建多少个连接

设置:
- 根据QPS和查询时间计算
- 不要超过MySQL max_connections的80%

示例:
峰值QPS=1000,查询时间=10ms
maximum-pool-size = 1000 × 0.01 × 2 = 20

minimum-idle(最小空闲连接)

作用:连接池维持的最少空闲连接数

设置:
- 通常设置为maximum-pool-size的20-50%
- 避免突发流量时临时创建连接

示例:
maximum-pool-size = 50
minimum-idle = 1020%)

connection-timeout(连接超时)

作用:等待连接的最大时间

场景:
1. 请求获取连接
2. 连接池已满,等待
3. 等待30秒后,仍没有空闲连接
4. 抛异常:HikariPool - Connection is not available

推荐:
- 短连接:3-10秒
- 长连接:10-30秒

🎯 监控连接池状态

监控关键指标

@Component
public class HikariPoolMonitor {
    
    @Autowired
    private HikariDataSource dataSource;
    
    @Scheduled(fixedDelay = 10000)  // 每10秒
    public void monitor() {
        HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();
        
        int totalConnections = poolMXBean.getTotalConnections();    // 总连接数
        int activeConnections = poolMXBean.getActiveConnections();  // 活跃连接数
        int idleConnections = poolMXBean.getIdleConnections();      // 空闲连接数
        int waitingThreads = poolMXBean.getThreadsAwaitingConnection();  // 等待连接的线程数
        
        log.info("连接池状态 - 总:{}, 活跃:{}, 空闲:{}, 等待:{}", 
            totalConnections, activeConnections, idleConnections, waitingThreads);
        
        // 告警
        if (waitingThreads > 0) {
            log.warn("有{}个线程在等待连接,连接池可能不够", waitingThreads);
        }
        
        if (activeConnections == totalConnections) {
            log.warn("连接池已满负荷运行");
        }
    }
}

监控指标

指标正常值异常值说明
活跃连接数< 最大连接数的80%>= 最大连接数连接不够
空闲连接数> 0= 0连接全部被占用
等待线程数0> 0有请求在等待连接
连接获取时间< 10ms> 100ms连接池压力大

🎯 连接池配置的最佳实践

实践1:根据并发量设置

# 低并发(QPS < 100)
hikari:
  maximum-pool-size: 10
  minimum-idle: 5

# 中并发(QPS 100-1000)
hikari:
  maximum-pool-size: 50
  minimum-idle: 10

# 高并发(QPS > 1000)
hikari:
  maximum-pool-size: 200
  minimum-idle: 50

实践2:连接超时设置合理

hikari:
  connection-timeout: 10000  # 10秒
  
  # 如果10秒还拿不到连接,说明:
  # 1. 连接池太小
  # 2. 数据库查询太慢
  # 3. 出现了慢查询或死锁

实践3:监控和告警

// Prometheus监控
@Bean
public MeterBinder hikariMetrics(HikariDataSource dataSource) {
    return new HikariCPMetrics(dataSource);
}

// Grafana配置告警:
// - 活跃连接数 > 最大连接数的80%
// - 等待线程数 > 0
// - 连接获取时间 > 100ms

实践4:多数据源独立连接池

// 主库连接池(写)
@Bean
@Primary
public DataSource masterDataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://master:3306/db");
    config.setMaximumPoolSize(50);  // 写操作多
    return new HikariDataSource(config);
}

// 从库连接池(读)
@Bean
public DataSource slaveDataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://slave:3306/db");
    config.setMaximumPoolSize(100);  // 读操作更多
    return new HikariDataSource(config);
}

🎯 MySQL端的max_connections

应用连接池 vs MySQL max_connections

关系

应用连接池总数 < MySQL max_connections × 0.8

示例:
10台应用服务器
每台连接池:50
总连接数:10 × 50 = 500

MySQL max_connections >= 500 / 0.8 = 625

推荐设置:
max_connections = 1000

查看MySQL连接状态

-- 当前连接数
SHOW STATUS LIKE 'Threads_connected';

-- 历史最大连接数
SHOW STATUS LIKE 'Max_used_connections';

-- 连接使用率
SELECT 
  @@max_connections AS max_conn,
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status 
   WHERE VARIABLE_NAME = 'Threads_connected') AS current_conn,
  ROUND((SELECT VARIABLE_VALUE FROM performance_schema.global_status 
         WHERE VARIABLE_NAME = 'Threads_connected') / @@max_connections * 100, 2) AS usage_pct;

+----------+--------------+-----------+
| max_conn | current_conn | usage_pct |
+----------+--------------+-----------+
| 500      | 423          | 84.60     |  ← 使用率84.6%,接近上限
+----------+--------------+-----------+

🎓 面试标准答案

题目:为什么MySQL连接数过小会导致查询变慢?

答案

原因

  1. 连接池排队

    • 并发请求 > 连接池大小
    • 请求等待空闲连接
    • 等待时间 >> 查询时间
  2. 示例

    • 100并发请求
    • 连接池只有10个连接
    • 前10个立即执行(5ms)
    • 后90个排队等待(平均250ms)
    • 总响应时间:250ms + 5ms = 255ms
  3. 连接创建开销

    • 如果连接池太小,频繁创建/销毁连接
    • 创建连接耗时:50-200ms
    • 影响性能

如何设置

公式

连接数 = 峰值QPS × 平均查询时间 × 2

或

连接数 = (CPU核心数 × 2) + 磁盘数

推荐值

  • 低并发:10-20
  • 中并发:20-50
  • 高并发:50-200

注意

  • 应用连接池总数 < MySQL max_connections × 0.8
  • 监控等待线程数(> 0说明连接不够)
  • 连接不是越多越好(MySQL有上限)

题目:HikariCP的核心参数如何配置?

答案

核心参数

参数推荐值说明
maximum-pool-size20-100根据并发量设置
minimum-idlemaximum的20-50%保持一定空闲连接
connection-timeout10000-30000ms等待连接的超时时间
idle-timeout600000ms(10分钟)空闲连接存活时间
max-lifetime1800000ms(30分钟)连接最大存活时间

配置示例

spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      connection-timeout: 10000
      idle-timeout: 600000
      max-lifetime: 1800000

监控指标

  • 活跃连接数
  • 等待线程数
  • 连接获取时间

🎉 结束语

一周后,哈吉米把连接池调大到50。

哈吉米:"连接池从10改成50后,100并发的响应时间从500ms降到60ms!"

南北绿豆:"对,连接池是性能的关键,配置不当会严重影响性能。"

阿西噶阿西:"记住:连接数要根据并发量设置,太小会排队,太大浪费资源。"

哈吉米:"还要监控等待线程数,大于0说明连接池不够。"

南北绿豆:"对,提前发现问题,比性能下降后再优化强100倍!"


记忆口诀

连接数过小排队等,请求阻塞响应慢
根据QPS和查询时间算,峰值乘以时间再乘二
HikariCP推荐公式,核心数乘二加磁盘
应用连接池总数,不超MySQL的八成
监控等待线程数,大于零要调大连接池