线上Java服务突然开始报数据库连接异常。
应用监控里看,HikariCP并没有打满。
单个实例最大连接数配置只有30。
活跃连接甚至只有十几个。
可MySQL这边却已经开始拒绝新连接:
Too many connections第一反应通常是:
我的连接池明明还有空位,为什么MySQL已经没连接可用了?
于是开始检查:
是不是HikariCP配置错了。
是不是某个服务发生连接泄漏。
是不是数据库突然变慢。
这些方向都可能有问题。
但这里最容易忽略的一点是:
应用看到的是“自己这一小池连接”,MySQL看到的是“整个实例上所有客户端连接的总和”。
单个应用连接池没有打满,和数据库总连接数有没有耗尽,是两回事。
一、先给核心判断:Pool没满,不代表数据库连接还有余量
假设一个Java服务配置:
每个Pod最大30条数据库连接。
如果只有一个Pod,理论上最多也就30。
看起来离MySQL的max_connections还远。
但Kubernetes里很少只有一个Pod。
如果这个服务有:
10个Pod。
那理论最大连接数已经变成:
10 × 30 = 300。
再加上:
订单服务。
支付服务。
后台任务。
定时任务。
BI查询。
运维客户端。
数据库管理平台。
其他微服务。
最后整个MySQL实例可能已经有几百甚至上千条连接。
所以某个Java服务自己的Dashboard显示:
“Pool使用率只有40%”
并不能说明:
MySQL全局连接容量还有40%。
这是两个完全不同的观察范围。
二、最常见的场景:Kubernetes扩容以后,数据库连接数一起翻倍
这类问题在容器环境里特别常见。
比如原来订单服务只有5个Pod。
每个Pod最大连接数30。
理论最大就是150。
某次高峰来了,HPA把Pod扩到15个。
业务吞吐确实提高了。
但数据库侧同时发生了一件事:
每个新Pod都会建立自己的连接池。
于是订单服务理论连接数从:
150
直接涨到:
450。
如果其他服务也在扩容,整个MySQL连接数很快就可能逼近上限。
这时候应用端看起来非常正常。
每个Pod的池都没满。
CPU也没打高。
接口甚至还没明显变慢。
但数据库已经开始:
全局连接容量不足。
这就是为什么微服务扩容不能只看:
应用CPU。
还要看:
数据库能不能承受扩容后的总连接数。
三、所以连接池大小不能只按“单实例”设计
很多项目会写:
maximumPoolSize = 50
然后觉得:
50不算大。
单看一个服务实例确实不大。
但真正应该计算的是:
Pool Size × 实例数量 × 服务数量
例如:
订单服务10个Pod,每个30。
支付服务8个Pod,每个30。
会员服务6个Pod,每个20。
已经是:
300 + 240 + 120 = 660。
再加:
后台任务。
运营系统。
报表。
运维工具。
很容易继续往上走。
所以数据库连接设计,本质上应该有一个:
Connection Budget——连接预算。
不是每个服务各自觉得“30应该够了”。
而是整个数据库实例一共有多少连接额度,再分给各个调用方。
四、第二种情况:连接不是太多,而是“占得太久”
还有一种现场:
连接池最大30。
平时活跃只有5到10。
某次故障以后,突然长期保持25到30。
但QPS并没有明显上涨。
这时候就要怀疑:
Connection Hold Time变长了。
比如原来一次数据库操作:
50ms释放连接。
现在因为慢SQL、锁等待、长事务,变成:
5秒。
一个请求占着连接不放的时间增加100倍。
哪怕QPS不变,需要同时存在的连接数量也会大幅增加。
于是一个服务开始持续占住大量连接。
多个服务叠加以后,MySQL总连接数就可能迅速上升。
所以排查连接数问题,不能只看:
连接有多少。
还要看:
每条连接被占用了多久。
五、最危险的一类:事务里夹了HTTP、RPC或者其他慢操作
比如代码逻辑是:
开启事务。
查数据库。
更新订单。
调用第三方接口。
等返回。
再写数据库。
最后提交事务。
从业务代码看,很顺。
但在第三方接口等待的几秒钟里:
数据库连接一直被事务占着。
假设第三方服务偶尔从200ms变成5秒。
数据库连接占用时间也跟着暴涨。
最后就可能形成:
请求越来越多
→ 连接长期不释放
→ Pool越来越满
→ 更多请求等待连接
→ 请求耗时继续增加
最终从单个服务传导到MySQL:
总连接数越来越高。
所以看到Too many connections以后,我会特别查:
Transaction Scope是不是过大。
六、第三类问题:连接泄漏
这也是经典原因。
应用从池里拿了一条连接。
正常情况下用完以后应该归还。
但某个异常分支没有释放。
或者某段老代码手工管理Connection时遗漏关闭。
于是连接数量:
只增加。
不回来。
刚开始每天只漏几条,不明显。
运行几天以后:
连接池越来越紧。
服务越来越容易获取连接失败。
如果多个实例都有同样问题,数据库总连接数也会一直往上涨。
这类问题最明显的特征通常是:
连接数量随运行时间持续增加,而且和业务流量没有明显对应关系。
如果重启应用以后连接数立刻下降,然后过几个小时、几天又慢慢升起来,就很值得怀疑泄漏。
七、为什么“连接池没满”时也可能是连接泄漏?
因为你看到的可能只是:
某一个Pod。
比如总共有20个Pod。
你检查的这个Pod:
12 / 30。
确实没满。
但另外几个Pod可能已经:
30 / 30。
还有几个旧Pod正在Terminating,却暂时没释放数据库连接。
所以排这种问题不能只挑一个实例看。
要看:
整个服务所有实例的连接分布。
如果是Kubernetes环境,还要确认:
旧Pod退出时连接有没有及时断开。
滚动发布期间,新旧Pod短时间同时存在,数据库连接数同样可能突然增加。
八、第四种情况:真正占连接的根本不是你的业务服务
这是特别容易误判的一类。
应用团队看到MySQL:
Too many connections。
第一反应就去查Java连接池。
最后发现所有服务都正常。
继续看MySQL当前连接来源,才发现:
某个BI平台突然跑了大量查询。
或者:
开发人员开了几十个数据库客户端窗口。
定时数据任务突然并行启动。
运维脚本异常重试。
监控系统创建大量短连接。
所以真正排查时,最重要的一步是:
查清楚“到底是谁占了这些连接”。
而不是默认:
肯定是当前报错的Java服务。
九、最快排查顺序,我建议直接按这7步走
以后遇到:
应用连接池没满,但MySQL报Too many connections
我建议按这条链排。
第一步,看MySQL当前总连接数,以及距离max_connections还剩多少。
第二步,按:
用户。
Host。
数据库。
客户端来源
拆分,确认是谁占连接。
第三步,计算:
服务实例数 × 单实例Pool Size。
尤其看最近有没有HPA扩容、滚动发布。
第四步,看连接状态。
大量Sleep?
还是大量正在执行?
第五步,看长事务、慢SQL和锁等待。
确认连接是不是被长期占住。
第六步,看有没有连接泄漏或者退出实例没有及时释放连接。
第七步,再决定:
调连接池。
优化事务。
限制扩容。
还是重新规划数据库连接预算。
十、一个非常典型的现场
假设MySQL:
max_connections = 800
订单服务:
10个Pod × 30 = 300。
支付服务:
8个Pod × 30 = 240。
会员服务:
5个Pod × 20 = 100。
理论上已经:
640。
平时因为连接池不会全部打满,所以实际只用400左右。
某天高峰:
订单服务从10个Pod扩成15个。
理论容量又多出150。
同时BI任务开始跑报表。
再加几十条运维连接。
最后数据库连接数逼近800。
某些新请求开始建立连接失败。
但你去看某个订单Pod:
只用了18 / 30。
于是觉得:
“不可能是连接数问题。”
其实数据库看到的是:
所有客户端总和。
这就是这类故障最典型的错位。
十一、不要一看到Too many connections就直接提高max_connections
这确实是最直接的办法。
800不够?
改成1200。
短期业务可能恢复。
但数据库连接并不是免费的。
每条连接都会消耗:
内存。
线程或会话资源。
Buffer。
状态管理成本。
更重要的是:
如果根因是慢SQL和长事务,把上限调高以后,你可能只是允许:
更多请求同时进入数据库。
最终把CPU、IO、锁竞争一起推得更高。
所以提高max_connections之前一定要先问:
这些连接是合理容量,还是异常堆积?
如果是前者,可以做容量调整。
如果是后者,扩上限只是在延迟事故。
十二、Sleep很多,是不是就能全部杀掉?
也不能简单这么处理。
连接池里的空闲连接,在MySQL侧本来就可能表现为Sleep。
Sleep不等于连接泄漏。
真正要结合:
连接年龄。
客户端来源。
连接池设计。
业务访问模式
一起判断。
如果某个服务平时就是保留20条连接池,那么看到20条Sleep很正常。
如果一个脚本突然留下500条Sleep,而且持续不释放,那才值得处理。
所以真正的问题不是:
Sleep多不多。
而是:
这些Sleep是不是符合预期连接预算。
十三、连接池到底应该设置多大?
没有一个统一数字。
并不是:
CPU核数 × 2
就永远正确。
也不是:
越大吞吐越高。
连接池太小:
请求经常排队等Connection。
太大:
数据库同时承受太多并发查询,反而可能更慢。
真正应该结合:
数据库能力。
SQL耗时。
业务QPS。
事务长度。
服务实例数。
扩缩容上限
一起设计。
特别是在Kubernetes里,不要只设置:
每个Pod最多30。
还要问:
最极端扩容到多少Pod?
如果最大50个Pod:
那就是1500条潜在连接。
这个数字数据库是否承受得住?
这才是应该提前算的。
十四、怎么修?
如果根因是Pod扩容导致连接总量失控,可以降低单实例Pool Size,或者给服务设置合理的扩容上限。
如果根因是长事务,缩短Transaction Scope,不要在事务里等待不必要的HTTP/RPC。
如果是慢SQL,让连接尽快完成工作并释放。
如果是连接泄漏,先找到未释放路径,而不是继续加Pool Size。
如果是BI、任务、脚本抢占连接,要给不同调用方做连接配额或者隔离。
如果数据库本身的正常业务量确实增长了,再结合资源评估调整max_connections。
真正修复目标是:
让数据库总连接需求重新回到一个可预测、可预算的范围。
十五、修完以后怎么验证?
不要只看:
Too many connections不报了。
还要继续观察:
MySQL总连接数。
各客户端连接占比。
应用Pool Active / Idle / Pending。
连接获取等待时间。
长事务数量。
SQL耗时。
Kubernetes Pod数量。
高峰期连接数曲线。
最重要的是重新压一次真实高峰模型。
比如:
正常10个Pod。
扩到20个Pod。
再叠加后台任务。
看看数据库连接数是否仍然有足够余量。
这比平时低流量下看连接池“很健康”更有价值。
十六、ChatGPT、Codex在这种问题里怎么用更有效?
不要只告诉ChatGPT、Codex:
MySQL Too many connections怎么办?
最好一起给:
max_connections。
当前总连接数。
各服务Pod数量。
HikariCP最大连接数。
连接状态分布。
长事务。
慢SQL。
最近HPA和发布记录。
这样AI才能帮你快速判断:
是连接预算失控。
事务占用过久。
Pool设置过大。
连接泄漏。
还是外部工具抢连接。
如果Deployment、HikariCP配置都在仓库里,Codex还能继续帮你算:
最坏情况下整个服务理论上能创建多少数据库连接。
这往往比只看某个Pod更有价值。
十七、Plus和Pro怎么判断?
如果你只是偶尔让ChatGPT、Codex:
分析一次数据库连接满。
看看HikariCP。
排一个长事务。
算一下Pod数量和连接池容量。
这种任务比较集中,Plus通常已经够用。
如果日常已经变成:
大量Java微服务共享数据库。
需要持续分析连接池、Kubernetes扩容、SQL、事务、数据库资源和容量规划。
还要让Codex修改配置、优化事务、做压测和容量验证,
这种跨应用、跨数据库、多轮工程排障已经成为常态,
那Pro会更适合。
真正的判断标准不是:
MySQL连接数高不高。
而是:
ChatGPT、Codex是不是已经持续参与完整的数据库容量和应用连接治理。
最后
应用连接池明明没满,为什么MySQL还是会报:
Too many connections?
因为:
单个应用Pool的使用率,只代表这个实例自己的连接情况。
MySQL真正限制的是:
整个数据库实例上所有客户端连接的总和。
真正应该一起看的,是:
服务实例数 × Pool Size
再加上:
长事务。
连接泄漏。
滚动发布。
HPA扩容。
BI。
脚本。
其他微服务。
所以最快的排查链可以记成:
MySQL总连接数 → 谁在占 → Pod × Pool → 长事务 → 泄漏 → 外部客户端 → 容量预算
只要把视角从:
“我这个连接池满没满”
切换到:
“整个MySQL实例到底被多少客户端同时占用”
很多看起来矛盾的Too many connections,就会立刻变得清楚。