1. 连接池基础概念与工作原理

让我先问你一个问题:你写后端服务的时候,每次数据库操作都新建连接、用完后直接关闭吗?

如果是,那你一定遇到过类似的场景——并发一上来,系统响应突然变慢,数据库那边也开始报警。嗯,我当年刚入行时就这么干过,结果被线上事故狠狠教育了一次。

今天这一章,咱们先把连接池的底子聊透。概念搞明白了,后面讲调优才站得住脚。

1.1 什么是连接池?

说白了,连接池就是一个“连接缓存”。它预先建立一批数据库连接,放进一个池子里。你的应用要连接数据库时,直接从池子里拿一条用,用完再还回去。

你不用每次都经历三次握手、认证鉴权这些开销。这样一来,响应快了,资源利用率也上去了。

核心思路:“复用”代替“反复创建”。连接池的本质,就是用空间换时间。

记得 2017 年我负责过一个电商中台项目。刚开始没用连接池,每个请求都新建连接。上线那天被 200 个并发打崩了。后来加了连接池,同样的机器配置,扛住了 2000 并发。差别就这么大。

1.2 为什么非用连接池不可?

我们对比一下两种做法的开销:

操作 无连接池 有连接池
TCP 三次握手 每次都要 只需要一次(建池时)
数据库认证 每次都要 只需要一次
连接释放 close() + 四次挥手 归还到池中
高并发下的连接数 随请求暴涨 控制在固定范围

你想想看,每次请求省掉这几次网络往返,对于高 QPS 的系统来说,就是毫秒级的生死差距。我个人习惯把连接池放到“架构基础设施”这个层级来看——它不显眼,但一旦出问题,全是大事。

避坑指南:我曾经见过一个团队,所有服务都连同一个 MySQL 实例。结果某个服务没配连接池,一上线直接把数据库的连接数打满,导致整个业务停摆。嗯,连接池不是锦上添花,它是保命的底线。

1.3 连接池的核心参数

搞明白了为什么用连接池,咱再看实际配置时该关注哪些参数。我用一张表给你说明白:

参数名 含义 我见过最坑的配置
maxTotal 池中最大连接数 设得比数据库 max_connections 还大 — 这是自爆
maxIdle 池中最多保留多少空闲连接 设成 0,等于把连接全关了,每次重新创建
minIdle 池中至少保留多少空闲连接 设得太大,低峰期浪费资源
maxWaitMillis 拿不到连接时最多等多久 设成 -1(无限等待),导致线程全挂住
timeBetweenEvictionRunsMillis 空闲连接回收检查的间隔 设成 0 或负值,相当于关掉了回收检测

我自己踩过的坑,最典型的就是 maxWaitMillis 没设。业务高峰期,几十个线程都卡在“等连接”这一步上,服务假死。从那以后,我但凡接入连接池,第一件事就是检查三个值:maxTotal、maxWaitMillis、minEvictableIdleTimeMillis。

个人建议:maxTotal 建议设置为数据库 max_connections 的 70%~80%,留点余量给运维和监控工具。别贪心,把配置吃满。

1.4 连接池的工作原理

我们拆开来看一个连接池完整的生命周期。

  1. 初始化:应用启动时,连接池按 minIdle 创建一批连接。
  2. 借出连接:应用请求连接时,从池中取一个空闲连接。如果没有空闲且未达到 maxTotal,则新建一个。
  3. 使用连接:应用执行 SQL,正常读写。
  4. 归还连接:用完后调用 close()。有连接池时,close() 其实不是真关闭,而是把连接状态重置后放回空闲队列。
  5. 回收空闲连接:后台会有一个 eviction 线程,定时检查空闲连接。超过 minEvictableIdleTimeMillis 还没被使用的连接,会被真正关闭。

这个过程我以前总以为很简单,直到有一次排查问题,发现连接池里全是“假空闲”的连接——实际 TCP 连接早就断了,但池子不知道。从那时起我才意识到,连接池的“健康检查”有多重要。

// 借-还连接的典型流程(伪代码)
Connection conn = connectionPool.getConnection();  // 从池中借出
try {
    conn.execute(sql);                            // 执行业务逻辑
} catch (SQLException e) {
    // 异常处理
} finally {
    conn.close();                                 // 归还到池中(不是真关)
}

关键点:finally 块中调用 close() 是不能省略的。否则连接永远不会归还池中,资源很快耗尽。

1.5 常见连接池实现对比

连接池 默认线程模型 内置监控 性能表现
HikariCP FastList + ConcurrentBag 支持 JMX、Micrometer 吞吐量极高
Druid ReentrantLock + Condition 内置 Web 监控页面 稳定、功能丰富
Tomcat JDBC Pool 公平锁 + 同步队列 支持 JMX 中等
DBCP2 传统同步锁 基本监控 较慢,建议慎用

HikariCP 现在几乎是 Spring Boot 默认的推荐连接池。它的设计很巧妙——用 ConcurrentBag 代替了传统的 BlockingQueue,减少锁竞争。如果你用的是 Spring Boot 2.0+,默认就是它,但也要做参数调优,否则还是白搭。

1.6 知识体系总览

这一章的内容,我用一张图串起来给你看:

连接池核心知识体系 连接池 核心参数配置 工作原理流程 常见实现对比 maxTotal / maxIdle minIdle / maxWaitMillis 空闲回收参数 初始化 → 借出连接 使用 → 归还连接 空闲回收 → 健康检查 HikariCP Druid Tomcat JDBC / DBCP2 连接池 = 复用 + 限流 + 健康管理 用对参数,才能发挥连接池的真正价值

图里我把连接池的三大块——参数、流程、实现——都列出来了。你后续调优的时候,就可以对着这张图去检查自己的配置缺了哪一块。

1.7 这一章的核心收获

  • 连接池的本质是连接复用,减少重复建连的开销。
  • 核心参数里,maxTotal 和 maxWaitMillis 是最容易出问题的两个。
  • 借还流程中 finally 里写 close() 是基本素养,漏了就是生产事故。
  • 现代主流推荐 HikariCP,但要结合业务场景做参数微调。

一个小建议:如果你刚开始接手一个老系统,别急着改代码。先去连接池的配置看一眼——往往调几个参数就能让性能翻倍。我优化过的好几个项目,第一步都从这里开始。