☰
ChatGPT、Codex开发实战:没有慢SQL,为什么数据库连接池还是越来越满?
2026/10/1 8:28:46 网站建设 项目流程

线上接口开始变慢。

你第一反应是查数据库。

慢SQL列表几乎是空的。

大部分SQL都只有几十毫秒。

数据库CPU也没有明显打满。

可连接池监控却越来越奇怪:

Active Connection一路上涨。

Idle越来越少。

Pending Thread开始出现。

最后很多请求不是卡在SQL执行,而是卡在:

getConnection()

最让人困惑的是:

SQL明明都很快,为什么数据库连接还是迟迟不归还?

如果这时候直接让 ChatGPT、Codex:

“帮我优化SQL。”

很可能方向就错了。

因为连接池真正关心的,不只是:

SQL执行了多久。

而是:

一条连接从借出去,到最终归还,一共被占用了多久。


一、先给核心判断:SQL快,不等于连接占用时间短

很多人会把这两个时间混在一起。

比如一条SQL执行:

30ms。

看起来很快。

但如果整个事务是:

拿连接。

执行SQL。

调用HTTP接口。

处理业务逻辑。

再执行第二条SQL。

最后Commit。

整个过程可能持续:

2秒。

甚至5秒。

这意味着:

虽然真正跑SQL只用了几十毫秒,

这条数据库连接却可能被这个请求占了几秒。

所以真正应该看的不是:

SQL Duration

而是:

Connection Hold Time——连接持有时间。


二、最常见的现场:事务里夹了远程调用

比如代码逻辑大概是:

先查订单。

然后调用支付接口。

再更新状态。

最后提交事务。

如果整个方法被@Transactional包住,

那数据库连接可能从第一次访问数据库开始,就一直被占着。

中间调用支付接口花了1.5秒。

连接也跟着等了1.5秒。

如果并发一上来:

20个线程。

20个连接。

大家全都拿着连接等HTTP。

这时候数据库本身可能非常闲。

但连接池已经满了。

于是后面的请求全部卡在:

等连接。

这就是为什么:

Database不忙,不代表Connection Pool不忙。


三、所以第一步不要先查慢SQL,先看连接池四个指标

我通常先看:

  • Active Connection
  • Idle Connection
  • Pending / Waiting Thread
  • Connection Acquire Time

如果出现:

Active长期接近Max。

Idle接近0。

Pending持续上涨。

Acquire Time越来越高。

那问题已经很明确:

应用拿连接的速度,大于归还连接的速度。

这时候继续盯数据库CPU,很容易浪费时间。


四、为什么单条SQL几十毫秒,连接池还是能被拖满?

假设连接池大小:

20。

每个请求真正SQL执行:

50ms。

如果连接拿到以后立即执行、立即归还,

吞吐其实很高。

但如果每个请求拿着连接平均2秒:

20个连接最多只能同时服务20个请求。

第21个请求开始:

只能等。

如果请求继续进来,

Pending就会越来越多。

所以连接池容量真正受影响的是:

每个请求占用连接的总时间。

而不是SQL自己执行多久。


五、第二个高频原因:事务边界太大

很多项目里会出现这种写法:

一个Service大方法整个加:

@Transactional

然后这个方法里面做很多事情:

查数据库。

调用RPC。

计算。

写日志。

发消息。

再更新数据库。

从业务角度看:

“这是一整个流程。”

但从资源角度看:

这可能意味着:

一条数据库连接被整个流程长期占用。

事务越大,

锁持有时间通常越长。

连接持有时间也越长。

这时候连接池压力就会明显放大。

所以排查连接池时,一定要问:

Transaction到底从哪里开始,到哪里结束?

不要只看SQL本身。


六、真实边界场景:连接泄漏

还有一种情况就更直接。

连接拿出来了。

但某条异常路径没有正确关闭。

或者:

ResultSet。

Statement。

Streaming Cursor

没有释放。

于是这些连接一直回不到池里。

这种问题的特点通常是:

Active Connection缓慢上涨。

Idle越来越少。

但即使流量下降以后:

Active也不明显回落。

这时候就要开始怀疑:

Connection Leak。

很多连接池都有Leak Detection能力。

例如可以监控:

一条连接被借出超过多少秒还没有归还。

如果这种日志持续出现,

就很值得顺着调用栈查。


七、Streaming Result也很容易让连接“看起来没问题,其实一直占着”

有些查询结果很多。

为了避免一次性加载全部数据,会使用:

流式读取。

Cursor。

边读边处理。

这种方式本身没问题。

但只要数据还没有读取完,

连接往往就不能归还。

如果你一边读数据库,一边:

做复杂计算。

调用外部接口。

写文件。

那这条连接可能会被长时间占用。

所以有时候:

SQL执行本身已经开始返回数据。

但Connection仍然没有结束生命周期。

这类场景特别容易被“没有慢SQL”误导。


八、还有一种情况:线程池比连接池大得多

比如:

业务线程池有100个线程。

数据库连接池只有20个连接。

一旦100个任务同时进来:

20个拿到连接。

剩下80个开始等。

如果这些任务本身还包含远程调用、长事务,

等待会越来越严重。

这时候你可能以为:

连接池太小。

直接改成:

20 → 50。

但问题可能只是被暂时推迟。

因为数据库本身能不能承受50个并发连接,

还要另外判断。

所以:

Thread Pool和DB Pool必须一起看。

不能各调各的。


九、为什么“把连接池调大”经常治标不治本?

这是最常见的第一反应。

Active满20。

那就改50。

如果问题只是:

连接池确实偏小,

当然可能有效。

但如果真正原因是:

长事务。

连接泄漏。

HTTP调用放在事务里。

Streaming Result占用太久。

那Pool变大以后只是:

原来20个请求一起卡。

现在50个请求一起卡。

甚至数据库压力变得更大。

最终可能出现:

连接池不排队了。

数据库开始排队。

所以调大Pool之前,一定要先回答:

为什么连接迟迟不归还?


十、最快排查顺序,我一般按这7步

遇到“没有慢SQL,但连接池越来越满”,我建议直接按这个顺序查。

第一步:看Pool状态

确认:

Active、Idle、Pending、Max。

第二步:看Connection Acquire Time

判断请求是不是已经开始大量等连接。

第三步:看事务耗时

不是SQL耗时。

是完整Transaction Duration。

第四步:看Connection Hold Time

连接拿到以后到底多久才归还。

第五步:检查事务里有没有远程调用

HTTP。

RPC。

消息。

文件IO。

第六步:检查Leak

看连接是否存在长时间不归还的异常路径。

第七步:再看线程池和数据库容量

最后才决定Pool到底应该配多大。

这个顺序比:

“先看慢SQL → 没有 → 调大连接池”

要稳很多。


十一、具体怎么修?先看根因是哪一类

如果是:

事务太大

缩小事务边界。

把非数据库操作尽量移出事务。

HTTP / RPC夹在事务里

尽量先完成远程交互,再进入真正需要原子性的数据库阶段。

Connection Leak

补全关闭逻辑。

检查异常路径。

开启Leak Detection。

Streaming Result占用过久

缩短处理时间。

分页。

批次读取。

不要边占连接边做大量非数据库工作。

Pool确实偏小

在确认数据库承载能力以后,再合理调整。

不是盲目翻倍。


十二、还有一个很实用的判断:流量下降以后,Active会不会降下来?

这能快速区分很多问题。

如果高峰过去以后:

Active很快从20降到3。

Idle恢复。

那更像:

瞬时并发 + 容量不足。

如果高峰过去以后:

Active还长期维持19、20。

那就要重点怀疑:

长事务。

连接泄漏。

卡死调用。

未关闭资源。

这个现象非常有用。


十三、修完以后怎么验证?

不要只看:

“接口现在快了。”

更应该重新制造原来的并发。

然后观察:

Active是否会在峰值后回落。

Pending是否减少。

Acquire Time是否下降。

Transaction P95 / P99是否下降。

连接泄漏日志是否消失。

数据库CPU、IO是否仍然健康。

最重要的是:

Completed Request Rate能不能稳定高于进入速率。

否则Queue和Pending迟早还会再涨。


十四、一个典型修复前后的差异

修复前:

连接池Max = 20。

Active长期20。

Pending = 80。

SQL平均30ms。

Transaction平均2.3s。

修复以后:

把事务里的HTTP调用移出去。

Transaction从2.3s降到180ms。

结果即使连接池仍然是20:

Active峰值可能只到12。

Pending直接接近0。

这就说明:

真正的问题从来不是:

连接数量太少。

而是:

每条连接占用得太久。


十五、这种连接池排查,Plus和Pro怎么判断?

如果你平时只是偶尔让 ChatGPT、Codex:

分析连接池监控。

看看事务边界。

检查一段@Transactional代码。

辅助判断是不是Leak。

这种任务范围比较集中,Plus通常已经够用。

关键是把Evidence给完整:

Pool指标。

事务耗时。

线程池大小。

DB Pool大小。

代码调用链。

远程调用位置。

这些信息比只给一句:

“连接池满了”

有用得多。


如果你的日常已经变成:

持续排查数据库连接、线程池、事务、Redis、HTTP依赖。

一个问题需要结合:

Trace。

Thread Dump。

连接池指标。

数据库监控。

应用代码。

还要让Codex修改事务边界、补压测、做回归,

而且这种多轮工程任务每天都在发生,

那Pro会更适合。

所以Plus还是Pro,不看:

“连接池问题高级不高级。”

而是看:

ChatGPT、Codex是不是已经长期参与完整的工程排障链路。

偶发排查、短任务:

Plus通常够用。

高频性能治理、多轮分析验证:

再考虑Pro。


最后

没有慢SQL,

为什么数据库连接池还是越来越满?

因为真正决定连接池压力的,不只是:

SQL执行多久。

而是:

每条连接到底被占用了多久。

一个30ms的SQL,

完全可能因为:

长事务。

HTTP调用。

Streaming Result。

连接泄漏

让连接被占2秒、5秒甚至更久。

所以以后看到:

Active越来越高。

Pending越来越多。

不要第一反应只去查慢SQL。

先把:

Connection Acquire Time → Transaction Duration → Connection Hold Time → 外部调用 → Leak

这一条链还原出来。

很多“数据库不忙,但连接池爆了”的问题,真正根因往往就在这里。

持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了
稳定的Plus/Pro会员订阅渠道,有需要可自取。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询