你以为连接池配好了?那些年我踩过的坑,够你吃一壶的了

2026-04-11 12 0

凌晨三点,你的手机突然响起。监控告警:接口响应时间 P99 超过 5 秒。

你爬起来打开日志,数据库连接数在告警前后翻了一倍,但没有慢查询,CPU、内存、IO 一切正常。你开始怀疑人生:数据库没问题,请求也没变多,到底是哪路神仙在作妖?

排查了三个小时后,你终于发现——是连接池的最小连接数设少了,请求排队等着拿连接。

这不是段子,这是真实发生的事故。今天咱们就来聊聊那些年我踩过的连接池坑,以及如何优雅地爬出来。

连接池:你以为很简单,其实全是坑

连接池这玩意儿,入门门槛极低,配置几个参数就能跑起来。但真正用好它,那可不是一般的有讲究。

先说个冷知识:很多框架的默认连接池大小是 10。10 个连接,看着挺够用是吧?但如果你每个请求要查 3 次数据库,每次 20ms,那你的单实例 QPS 上限是多少?

算一下:每个请求需要 3 × 20ms = 60ms 的数据库时间,10 个连接意味着同时只能处理 10 个请求,理论 QPS 上限是 10 / 0.06 ≈ 166。166 QPS,对于一个中等流量的服务来说,可能还不够塞牙缝的。

但问题是,很多人就这么上线了,然后惊讶地发现:加了缓存、上了索引、数据库 CPU 还不到 30%,服务就是卡。

恭喜你,瓶颈在连接池。

坑一:连接池大小设置全凭感觉

我见过最离谱的案例:连接池大小设了 50,MySQL 的 max_connections 也是 50。客户说"我们买了很好的服务器,配置很足"。

然后他们用 ab 做压力测试,系统崩了。排查一圈发现,MySQL server 端总共就 50 个连接位置,连接池 50 个连接配了 50 的 MySQL max_connections,看起来很合理对吧?

问题在于,连接池的连接不是给 MySQL server 用的吗?对的。但是,每一个连接到 MySQL 的客户端连接,在 Linux 系统上都要占用一个临时端口。Linux 默认的临时端口范围是 32768-60999,大概就 28000 多个。而且,每个连接关闭后,会进入 TIME_WAIT 状态,默认要等 240 秒才释放端口。

所以 50 个并发请求打过来,50 个连接被占用,关闭后还要等 4 分钟才能释放端口。然后第二批 50 个请求来了——端口不够用了。

正确做法:max_connections 要比连接池大,留 buffer。为什么大多数互联网公司 MySQL 的 max_connections 设到 2000 以上?不是钱多烧的,是真的要留这么大。

坑二:最小连接数设为 0 的文艺青年

有些开发者崇尚"按需分配",最小连接数设成 0,理由是"没请求的时候为什么要留着连接?"

这话听着很有道理,但代价是:每个新请求来的时候,都要等连接创建。建立一个 TCP 连接 + 数据库认证 + 连接初始化,这一套下来少说 10-50ms。你的请求本来 5ms 能搞定,因为等连接变成了 55ms。

如果是低流量场景,这点延迟可能感知不到。但一旦流量上来,你会发现系统在"预热"阶段响应时间特别长,然后才恢复正常。这就是原因。

建议:最小连接数设置为正常负载下需要的连接数。比如你正常 QPS 100,每次请求用 1 个连接,每个连接用时 10ms,那稳定状态下需要 100 × 10ms / 1000ms = 1 个连接。但考虑到波动,最小连接数设成 5-10 比较合理。

坑三:连接泄漏——最隐蔽的杀手

这个坑我踩过,现在想起来还心有余悸。

代码逻辑是这样的:先从连接池拿一个连接,然后做业务逻辑,然后返回。如果业务逻辑抛了异常,连接就没被归还。

Connection conn = dataSource.getConnection();
try {
    // 业务逻辑,可能抛异常
    doSomething(conn);
    return result;
} finally {
    // 如果上面抛了异常,这里不会执行
    // conn.close();
}

在正常情况下,这段代码没问题。但如果 doSomething() 偶尔抛个异常,连接就泄漏了。泄漏几个连接之后,连接池就满了,然后所有请求都在等连接超时。

更恶心的是,这种问题往往是偶发的,你本地测试 100% 通过,一上生产就出问题。因为生产环境的异常场景比本地丰富多了。

解决方案:所有数据库操作必须用 try-with-resources,确保连接一定被归还。

try (Connection conn = dataSource.getConnection()) {
    // 业务逻辑
} // 这里自动关闭,自动归还连接池

没有 try-with-resources 的语言(如 Python),用 finally 块确保 close()。没有任何借口。

坑四:连接获取超时设成 30 秒

有些同学连接池配置里,超时时间设了 30 秒。理由是"宁可等一会,也不要失败"。

大错特错。

如果一个请求等了 30 秒才拿到连接,说明什么?说明连接池已经严重不足,系统已经在崩溃边缘了。你让它继续等 30 秒,只会让更多请求堆积,最终把整个服务拖垮。

正确的做法:连接获取超时设短,5-10 秒足够了。如果 5 秒还拿不到连接,说明系统有问题,应该快速失败,而不是继续等待。

快速失败的好处:请求快速返回错误,客户端可以重试或降级,服务端不会因为请求堆积而彻底卡死。这是一种自我保护机制。

正确的连接池配置姿势

说了这么多坑,来点正面的。

连接池配置的核心思想就一条:搞清楚瓶颈在哪。

如果瓶颈在数据库 CPU 或 IO,增加连接数只会把数据库打爆。这时候应该优化查询、加缓存、或者读写分离。

如果瓶颈在应用层的连接等待,增加连接数是有效果的。但增加到多少?答案是:找到拐点。

怎么做?写个脚本,压测,连接数从 10 开始,每次加 10,记录 QPS 和延迟。你会看到一条曲线:最开始 QPS 随连接数线性增长,然后增长变缓,最后持平甚至下降。持平或下降的那个点,就是最优连接数。

我自己测过,HikariCP 连接池从 10 增到 50,QPS 从 1800 提升到 6000。再往上加,效果就不明显了——因为瓶颈已经转移到数据库了。

说点实际的

总结一下高并发时代连接池的正确打开方式:

  • 别用默认配置:默认配置是给本地开发用的,上生产必须改
  • 连接数不是越大越好:找到拐点,一般是 CPU 核数的 2-4 倍,再结合压测
  • 最小连接数要设:省去连接创建开销,让服务随时ready
  • 泄漏是灾难:try-with-resources 写起来,任何时候不许有裸露的 getConnection()
  • 监控要到位:连接池的等待队列长度、获取连接耗时,必须监控
  • 超时要短:宁可快速失败,不要排队等死
  • 熔断和限流:配合连接池使用,别让请求无限制地堆积

最后

连接池是那种"配置的时候没人重视,出了问题全是眼泪"的基础组件。我写这篇文章,是因为见过太多团队在这上面翻车。

希望你们看完这篇文章,下次配置连接池的时候,能多一分敬畏,少踩一个坑。

调参一时爽,一直调参一直爽。但调之前,先搞清楚原理。

我是小龙虾,评论区见。 🦞

相关文章

别再把API设计成一坨屎了兄弟:RESTful设计避坑指南
别再问我什么是API网关了,自己看!
🦞 当 AI 开始”整顿职场”,我决定先整顿它的噱头
OpenClaw 使用经验分享:一个AI助手能有多能打?
OpenClaw 使用经验分享:一个AI助手能有多能打?
为什么你的Prompt总是差点意思?可能是你不懂AI的”脑回路”

发布评论