凌晨两点,钉钉响了。报警信息显示:数据库连接数突破 5000,MySQL 开始拒绝连接。
你从床上蹦起来,揉着眼睛打开电脑,心里已经在构思辞职信怎么写。
结果排查了一圈,发现问题根源是:连接池漏了。
是的,就是那个你配完就再也没管过的连接池。
连接池是个什么东西?
简单说,连接池就是应用程序和数据库之间的一个连接缓存池。
没有连接池的时候,每次查数据库都要走一遍:建立TCP连接 → 认证 → 执行SQL → 关闭连接。这个过程耗时 1-10ms 不等,高并发下光建连接就能把你耗死。
连接池就是在启动时预建一批连接,用完了还回去而不是真的关闭,下次再用直接从池里拿,省掉了建连接的开销。
听起来很美好对吧?但魔鬼都在细节里。
坑一:连接泄漏——最常见的生产事故
什么是连接泄漏?就是从池里借了连接,用完了忘记还回去。
听起来低级吧?但这绝对是生产环境排名第一的数据库问题。我见过的泄漏场景包括但不限于:
- 代码抛异常了,没有执行
finally { conn.close(); } - 逻辑分支提前 return 了,还没还连接
- 自定义线程池里用了数据库连接,线程结束连接没还
一次泄漏问题不大,池子大点能撑一阵。但如果是慢查询泄漏,每 10 秒漏一个,用不了多久池子就空了,后续请求全部排队等待。
来看一个典型的事故时间线:
23:00 - 上线新功能,某个异常分支忘记关闭连接
23:15 - 连接池 80% 已借出,开始出现等待
23:30 - 池子耗尽,新请求开始超时
23:45 - 监控报警,值班人员开始排查
00:20 - 定位到问题代码,热修复回滚
00:35 - 服务恢复
浪费了 35 分钟,这 35 分钟本可以通过配置改变。
怎么防?现代连接池基本都有泄漏检测功能。以 HikariCP 为例:
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
// 连接泄漏检测:超过 60 秒未归还则警告
config.setLeakDetectionThreshold(60 * 1000);
开启这个参数后,如果一个连接被借出超过 60 秒还没还,HikariCP 会打印一条警告日志,里面包含借出连接的代码位置。这招在我手上救过好几次。
坑二:连接池大小——拍脑袋重灾区
连接池多大合适?这个问题我问过很多后端,答案五花八门:
- 「50 应该够了吧」
- 「我们设了 200,能撑一阵」
- 「不知道,MySQL 默认 151,我设了 300 应该够了」
这些答案都有一个共同特点:没有思考过程。
连接池大小的计算其实有公式可循,但公式也要结合实际。核心逻辑是:连接数 = (核心数 * 2) + 硬盘速度因子。
等等,这个公式哪来的?实际上是 Oracle 官方推荐的公式:
connections = ((核心数 * 2) + 磁盘数量)
这个公式假设应用是 IO 密集型。如果你的应用是 CPU 密集型(纯计算不 IO),连接数应该更低,大概和核心数差不多就够了。
但问题是,大多数应用既不是纯 IO 也不是纯 CPU,所以这个公式只能当基准线,最终还是要靠压测和监控来校准。
我见过最离谱的一个案例:有个服务设了 maximumPoolSize = 1000,理由是「万一并发量上来了呢」。结果数据库服务器连接受不暇,查询延迟从 5ms 飙升到 500ms,整个集群差点雪崩。
连接池不是越大越好!每个连接都要消耗数据库端的内存(InnoDB 每个连接约 2-4MB),设太大反而会把数据库拖死。
坑三:Initial_size 被忽视的真相
很多连接池默认会在启动时建立一定数量的初始连接,这个值叫 initialSize 或 minimumIdle。
但我发现大量项目根本不配置这个值,或者设为 0,让连接池在运行时慢慢爬坡到最大值。
问题来了:如果你的 minimumIdle = 0,maximumPoolSize = 50,那么低负载时池子是空的,第一个请求来的时候才建连接。对于老应用(启动后第一个用户请求可能要等好几秒),这种延迟是致命的。
正确做法是设置合理的 minimumIdle,让它等于你的日常并发基准。如果日常 QPS 约 100,50 个并发就够了,那就设 minimumIdle = 30,留点余量。
坑四:没有监控等于盲人骑瞎马
说了这么多,最最重要的一点:你没有监控你的连接池。
连接池有哪些指标值得监控?
- 活跃连接数(active connections)——真正在使用中的连接
- 空闲连接数(idle connections)——池子里待命的连接
- 等待获取连接的线程数——这个数字 > 0 就说明池子不够用了
- 连接获取耗时——正常应该在 1ms 以内,超过 10ms 就该查查了
我的推荐是:Prometheus + Grafana 搭一个连接池监控面板,把这几个指标都加上告警。规则是这样的:
- 活跃连接数 > 最大池大小的 80% → 告警
- 等待线程数 > 0 → 告警
- 连接获取耗时 > 50ms → 告警
有了这套监控,你可以在用户感知到问题之前 30 分钟就发现苗头。
说点真心话
连接池这个话题,技术上一点都不性感。没有高并发那样激动人心的数字,没有微服务那样听起来就很厉害的概念。
但就是这么一个「不起眼」的东西,毁掉了无数个工程师的周末和夜晚。
我现在的习惯是:新项目上线,第一件事配好连接池监控;老项目 review,第一件事看连接池配置。宁可少写一个功能,也要把这个基础设施弄明白。
不然下次凌晨两点响起的,可能就是你的手机了。