文章目录
- 每日一句正能量
- 1. 背景与问题
- 1.1 连接和并发不是同一概念
- 1.2 为什么应用线程等连接不一定是坏事
- 2. 环境与数据
- 2.1 为什么要设置application_name
- 2.2 基础监控SQL
- 2.3 为什么state特别重要
- 3. 复现过程
- 3.1 微服务是如何不知不觉制造1950个连接的
- 3.2 P50:50个连接
- 3.3 P100:100个连接
- 3.4 P200:200个连接
- 3.5 P500:500个连接
- 3.6 P1000:1000个连接
- 3.7 为什么CPU 99%并不等于“CPU利用得很好”
- 3.8 为什么SQL执行计划没有变,性能却变差
- 4. 方案实施
- 4.1 第一原则:先做数据库级连接预算
- 4.2 max_connections不是应用预算本身
- 4.3 第二原则:从实例数反推单实例池
- 4.4 一个实用估算公式
- 4.5 第三原则:控制active,而不是只控制total
- 4.6 第四原则:在线和批处理分池
- 4.7 第五原则:连接池等待应成为削峰机制
- 4.8 第六原则:minimumIdle不能盲目等于maximumPoolSize
- 4.9 第七原则:扩容必须重新计算连接预算
- 4.10 第八原则:慢SQL会占住连接更久
- 4.11 第九原则:work_mem会被并发放大
- 4.12 第十原则:不要用max_connections掩盖连接池问题
- 5. 结果对比
- 5.1 原始状态
- 5.2 E1:每实例池减半
- 5.3 E2:增加数据库并发门禁
- 5.4 E3:批处理独立池
- 5.5 E4:回收大量idle连接
- 5.6 E5:只提高max_connections
- 5.7 汇总
- 5.8 为什么active峰值120比200更快
- 5.9 连接池利用率也要同步看
- 5.10 用等待事件解释为什么数据库慢
- 6. 风险与复盘
- 6.1 风险一:池调得太小
- 6.2 风险二:只调maxPoolSize,不看实例数
- 6.3 风险三:弹性扩容绕过数据库预算
- 6.4 风险四:连接池太大掩盖慢SQL
- 6.5 风险五:idle in transaction
- 6.6 风险六:超时设置互相打架
- 6.7 风险七:连接池启动风暴
- 6.8 风险八:连接泄漏
- 6.9 风险九:DBA连接被应用吃光
- 6.10 风险十:连接多导致内存预算失控
- 推荐治理模型
- 一个简单预算例子
- 推荐验收顺序
- 回退方案
- 最终结论
- 附录:最低验收门禁
每日一句正能量
“与独处相安,与万事言和。”
与自己和解,让内心成为平静的港湾。与世界和解,接纳生活的不完美与无常。
主题:连接管理 / 微服务集群 / 连接池
重点:max_connections、sys_stat_activity、active/idle、连接预算、上下文切换、内存放大、等待事件、连接池大小、P95/P99
适用场景:KingbaseES 上的 Java 微服务、Spring Boot/HikariCP、容器化集群、弹性扩容、多服务共享数据库、批处理与在线业务混合负载。
1. 背景与问题
微服务架构上线以后,数据库经常会遇到一种很反直觉的现象:
应用连接池开大以后 数据库没有更快 反而更慢例如单个服务实例:
maximumPoolSize=20看起来并不大。
但集群里可能有:
订单服务 40个实例 会员服务 30个实例 营销服务 20个实例 报表服务 10个实例如果每个实例都允许 20~30 个数据库连接,那么汇总以后很容易变成:
几百 甚至上千个连接开发侧常见判断是:
连接池不够 → 线程在等连接 → 把池调大但数据库侧真正的问题不是:
能不能接受这些TCP连接而是:
当大量连接同时进入 active 状态,它们会共同竞争有限的 CPU、内存、Buffer、IO、WAL、锁和存储队列;超过系统资源拐点后,连接越多,排队和调度成本越高。
KingbaseES 官方连接参数文档说明,max_connections决定数据库允许的最大并发连接数量;官方运维手册建议监控当前连接数,并把连接数超过max_connections的 80% 作为值得告警的资源耗尽信号之一。
这说明:
max_connections更像一个:
容量上限/保护边界而不是:
性能越大越好的旋钮1.1 连接和并发不是同一概念
数据库里:
500个连接不一定等于:
500个正在执行SQL可能:
400 idle 80 active 20 idle in transaction所以必须区分:
Total Connections Active Connections Idle Connections Idle in Transaction真正对 CPU、IO 和锁形成强竞争的通常是:
active连接。
但 idle 也不是完全免费,因为每个数据库后端仍有:
进程/会话状态 基础内存 连接槽位 系统资源如果几千个微服务连接长期常驻,也会消耗数据库容量。
1.2 为什么应用线程等连接不一定是坏事
一个典型误区:
连接池出现等待 = 池太小实际上数据库已经达到最佳并发时:
让一部分请求在应用连接池里排队往往比:
全部放进数据库内部排队更健康。
应用层等待通常:
成本更低 更容易超时 更容易削峰数据库内部过载则可能引发:
CPU打满 上下文切换 锁等待放大 IO队列 WAL压力 全业务尾延迟上涨所以连接池本质上不仅是:
连接复用器也是一个:
并发闸门2. 环境与数据
测试环境示例:
数据库: KingbaseES CPU: 32 Core 内存: 128GB max_connections: 1000 业务: 20个微服务实例参与本轮压测 SQL: 典型OLTP点查 + 更新 单SQL正常耗时: 2~8ms连接池实验:
P50: 总连接50 P100: 总连接100 P200: 总连接200 P500: 总连接500 P1000: 总连接1000固定:
请求模型 SQL 索引 数据分布 客户端QPS 数据库参数只修改:
允许进入数据库的并发连接2.1 为什么要设置application_name
微服务数据库连接一定要能识别来源。
sys_stat_activity官方字段包含:
application_name client_addr state query wait_event_type wait_event如果所有服务:
application_name都为空出了事故只能看到:
500个连接但不知道:
谁占了400个因此建议:
order-service member-service report-service分别设置清晰的应用名。
2.2 基础监控SQL
SHOWmax_connections;SHOWsuperuser_reserved_connections;查看连接状态:
SELECTstate,COUNT(*)FROMsys_stat_activityWHEREbackend_type='client backend'GROUPBYstate;按服务拆:
SELECTapplication_name,client_addr,state,COUNT(*)FROMsys_stat_activityWHEREbackend_type='client backend'GROUPBYapplication_name,client_addr,stateORDERBYCOUNT(*)DESC;KingbaseES 官方维护文档也直接推荐使用:
SHOWmax_connections;SELECT*FROMsys_stat_activity;查看数据库连接容量和会话状态。
2.3 为什么state特别重要
官方sys_stat_activity文档定义:
active idle idle in transaction idle in transaction (aborted)其中:
active代表正在执行查询。
idle代表等待客户端的新命令。
idle in transaction则代表:
事务还开着 但当前没有SQL执行这最后一种尤其危险。
上一篇已经讨论过,它可能同时拖住:
锁 VACUUM 连接槽位所以连接池治理必须和:
事务治理一起做。
3. 复现过程
3.1 微服务是如何不知不觉制造1950个连接的
假设:
订单: 40实例 × 20 = 800 会员: 30实例 × 15 = 450 营销: 20实例 × 20 = 400 报表: 10实例 × 30 = 300理论最大:
1950每个团队看自己:
15~30都觉得不大。
问题在于:
数据库看到的是总和更麻烦的是 Kubernetes/HPA 扩容。
假设订单服务:
40实例 →80实例数据库连接需求瞬间:
800 →1600应用扩容本来为了:
扛流量结果数据库却被连接池同步放大。
这就是微服务数据库最典型的:
Pool Multiplication问题。
3.2 P50:50个连接
示例:
Active Peak: 48 CPU: 48% TPS: 18k P95: 38ms P99: 65ms数据库还有大量余量。
这时池可能确实偏小。
3.3 P100:100个连接
Active: 96 CPU: 66% TPS: 31k P95: 54ms P99: 92ms吞吐大幅提升。
尾延迟仍稳定。
这可能是:
甜点候选区3.4 P200:200个连接
Active: 188 CPU: 82% TPS: 36k P95: 96ms P99: 210msTPS 从:
31k →36k继续增加。
但:
P99已经翻倍+说明并发开始接近资源边界。
3.5 P500:500个连接
Active Peak: 420 CPU: 97% TPS: 34k P95: 480ms P99: 1.3s关键现象出现:
连接翻倍+ TPS反而下降原因不是连接失败。
而是数据库已经进入:
过载区3.6 P1000:1000个连接
Active: 760 CPU: 99% TPS: 26k P95: 1.9s P99: 4.8s此时:
更多连接只是在制造:
更多同时竞争资源的执行单元数据库需要频繁在大量后端之间调度。
上下文切换、缓存局部性、锁竞争、IO队列和内存压力都会恶化。
3.7 为什么CPU 99%并不等于“CPU利用得很好”
高负载系统最容易犯这个错误。
CPU:
99%可能是:
有效计算也可能大量消耗在:
调度 上下文切换 锁竞争 cache miss 后台压力所以 OS 侧必须同时观察:
run queue context switches iowait load average 内存 swap不能只看:
CPU利用率3.8 为什么SQL执行计划没有变,性能却变差
这类事故很典型:
100连接: Index Scan Execution 5ms 500连接: 还是Index Scan P99却1秒+原因:
EXPLAIN描述单条SQL如何执行但系统性能还取决于:
有多少条SQL同时执行相同计划:
100个并发和:
500个并发并不是同一个系统状态。
因此本文虽然要求突出执行计划,但必须强调:
连接数过高属于“执行计划不变也会恶化”的典型资源竞争问题,计划要作为基线证据,但不能替代并发监控。
4. 方案实施
4.1 第一原则:先做数据库级连接预算
不是每个应用自己决定:
我需要100而是 DBA/架构先给数据库定义:
可用应用连接预算例如:
max_connections = 600预留:
超级用户/系统连接 20 DBA/运维 20 定时任务 20 应急空间 40应用总预算:
约500这个 500 才是所有微服务共同分享的上限。
4.2 max_connections不是应用预算本身
KingbaseES 官方参数还提供:
superuser_reserved_connections专门给超级用户保留连接槽。
所以绝对不能:
max_connections=600 然后应用池总和也配置600否则高峰时:
DBA都可能进不去生产预算必须留:
Emergency Headroom4.3 第二原则:从实例数反推单实例池
假设:
应用预算=500一共:
50个业务实例最简单平均:
10连接/实例但真实系统不能平均分。
例如:
订单: 高优先级 15 会员: 10 营销: 5 报表: 独立限制连接预算必须反映:
业务重要度 SQL平均耗时 峰值并发 批处理特征4.4 一个实用估算公式
可以从:
目标并发 ≈ QPS × 数据库平均服务时间估算起点。
例如:
QPS = 5000 平均DB占用 = 5ms理论并发:
5000 × 0.005 = 25加上:
波动 P95 事务 安全系数可能需要:
40~60个有效连接而不是:
500这个公式不是最终答案。
但它能帮助团队摆脱:
“线程有1000,所以连接池也要1000”的错误直觉。
4.5 第三原则:控制active,而不是只控制total
假设:
总连接300其中:
idle=200 active=100数据库可能运行很好。
另一套:
总连接200 active=190反而可能更危险。
因此生产必须监控:
Total Active Idle Idle in Transaction四个指标。
真正的吞吐拐点通常和:
Active Concurrency关系更强。
4.6 第四原则:在线和批处理分池
最危险:
报表批处理 和 在线订单共用同一个无限连接池。
夜间批任务:
瞬间拿200连接在线请求就开始:
锁等待 CPU争抢 P99上涨更合理:
OLTP池 Batch池 Report池分别限制数据库并发。
例如:
在线: 100 批处理: 20 报表: 10批处理慢一点:
可以不能拖垮在线核心接口。
4.7 第五原则:连接池等待应成为削峰机制
当:
数据库安全active=120应用突然来:
500个并发请求应该允许:
380个在线程/连接池队列等待而不是:
放500个SQL同时进数据库连接池等待时间需要有:
connectionTimeout避免无限排队。
如果拿不到连接:
快速失败/降级/排队比数据库全局雪崩更好。
4.8 第六原则:minimumIdle不能盲目等于maximumPoolSize
很多连接池:
minimumIdle=maxPoolSize会导致每个微服务实例启动以后:
立即建立满池连接100个实例 × 20:
直接2000个常驻连接即使当前 QPS 很低。
对于大规模微服务集群,要评估:
低minIdle 按需增长 idle timeout降低常驻连接浪费。
4.9 第七原则:扩容必须重新计算连接预算
Kubernetes HPA:
20 pods →40 pods如果每个:
maxPool=20数据库理论连接需求:
400 →800所以发布平台应该有:
实例上限 × pool size门禁。
否则应用自动扩容本身可能成为:
数据库事故触发器4.10 第八原则:慢SQL会占住连接更久
连接池大小不能独立于 SQL 性能。
假设:
100连接 SQL平均5ms容量很好。
如果某次发布:
SQL平均500ms同样100连接:
很快全部busy上游看到:
connection pool exhausted但根因其实是:
慢SQL所以连接池告警触发后要先看:
active SQL wait_event query duration而不是第一时间:
池加倍KingbaseES 官方性能文档也强调sys_stat_activity可以查看数据库有多少连接、连接状态和等待事件,并指出长时间执行的查询或事务可能拖累整个系统。
4.11 第九原则:work_mem会被并发放大
假设某类 Sort/Hash:
work_mem=64MB注意它通常不是:
一个数据库实例只用一次64MB而可能在:
多个连接 多个执行节点上使用。
如果:
500 active sessions都跑需要较多工作内存的查询。
潜在内存压力可能很大。
所以:
连接数和:
work_mem必须联合容量规划。
不要单独调。
4.12 第十原则:不要用max_connections掩盖连接池问题
典型事故:
500不够 →1000 →2000每次扩完:
短期不报“too many connections”但数据库:
P99越来越差最终还可能:
OS内存压力 上下文切换爆炸正确顺序应该:
看服务来源 看active比例 看慢SQL 看等待事件 调应用pool 限制并发 最后才评估max_connections是否真的容量不足5. 结果对比
以下为方法演示数据,并非生产实测。
5.1 原始状态
总连接: 900 Active: 420 TPS: 30k CPU: 97% P99: 2.6s应用团队认为:
连接池不够但数据库已经明显过载。
5.2 E1:每实例池减半
例如:
maximumPoolSize: 20 → 10集群:
总连接: 470 Active: 210结果:
TPS: 35k P99: 780ms连接少了。
吞吐反而提高。
这就是最有说服力的一组实验。
5.3 E2:增加数据库并发门禁
进一步让:
进入DB执行的active峰值控制在:
约120结果:
总连接: 430 Active: 120 TPS: 37k P99: 240ms说明最佳吞吐点不是:
最多并发而是:
资源不饱和情况下的有效并发5.4 E3:批处理独立池
报表/ETL:
最多20活跃连接在线:
独立100左右结果:
白天核心P99: 160ms批任务完成稍慢。
但:
生产SLA稳定整体收益更高。
5.5 E4:回收大量idle连接
设置合理:
minimumIdle idleTimeout maxLifetime之后:
总连接下降Active基本不变。
P99:
变化不大这是正常的。
因为 idle 回收主要改善:
连接槽 基础内存 后台进程数量 运维可控性而不是直接提高一条查询的执行速度。
5.6 E5:只提高max_connections
原先:
1000继续提高。
连接数进一步增加。
结果:
TPS不升 P99继续上升因为真正瓶颈:
CPU/IO/锁/调度没有改变。
5.7 汇总
| 连接/方案 | Active峰值 | CPU | TPS | P99 |
|---|---|---|---|---|
| 50 | 48 | 48% | 18k | 65ms |
| 100 | 96 | 66% | 31k | 92ms |
| 200 | 188 | 82% | 36k | 210ms |
| 500 | 420 | 97% | 34k | 1.3s |
| 1000 | 760 | 99% | 26k | 4.8s |
| 治理后 | 120 | 约70% | 37k | 240ms |
从曲线可以看出:
100→200 吞吐仍提高 200→500 开始进入过载 500→1000 连接更多 TPS反而下降5.8 为什么active峰值120比200更快
因为 200 active 时:
CPU已经82%再加:
锁 IO WAL 后台任务系统开始出现排队。
120 左右可能刚好让:
CPU保持高利用 又不会进入严重争抢这就是并发甜点。
5.9 连接池利用率也要同步看
应用侧至少暴露:
Active Idle Pending/Waiting Acquire Time Timeout Count如果数据库只有:
active 50而应用有大量线程在等:
确实可能池太小如果数据库:
active 400 CPU 99%应用还在等:
绝不能简单加池这两种“连接池等待”含义完全不同。
5.10 用等待事件解释为什么数据库慢
sys_stat_activity:
wait_event_type wait_event可以帮助区分:
CPU竞争 锁等待 IO WAL 客户端等不同瓶颈。
连接数只是:
症状入口最终还需要找到:
高并发在争抢什么6. 风险与复盘
6.1 风险一:池调得太小
如果安全并发:
150却把数据库全局限制成:
20CPU只有:
20%应用大量等待。
这也是错误。
治理目标不是:
连接越少越好而是:
找到资源甜点6.2 风险二:只调maxPoolSize,不看实例数
单实例:
10看起来很小。
如果:
200个实例仍然是:
2000连接所以配置项必须记录成:
pool size × max instance count6.3 风险三:弹性扩容绕过数据库预算
HPA必须有:
数据库容量约束否则前端越扩:
数据库越容易崩6.4 风险四:连接池太大掩盖慢SQL
慢SQL上线:
池耗尽如果先:
20→100只会让更多慢SQL同时进数据库。
CPU和IO更差。
应先确认:
为什么连接借出时间变长6.5 风险五:idle in transaction
池里的连接如果:
业务忘了提交状态会是:
idle in transaction这种连接不只占池。
还可能:
持锁 拖VACUUM是高优先级治理对象。
6.6 风险六:超时设置互相打架
需要一起设计:
HTTP timeout connection acquire timeout statement timeout lock timeout transaction timeout如果:
HTTP 1s 连接获取 5s请求早就断了。
线程还在等数据库连接。
最终制造:
幽灵负载6.7 风险七:连接池启动风暴
大规模滚动发布:
100个Pod同时启动每个:
minimumIdle=20数据库会突然收到:
2000个建连请求即使平时业务流量并不高。
所以发布平台最好:
分批启动连接池也要:
避免满池预热6.8 风险八:连接泄漏
代码没有及时:
close/return connection池逐渐耗尽。
这时症状也是:
等待连接不能误判为:
池太小应用要有:
leak detection和连接借用时长监控。
6.9 风险九:DBA连接被应用吃光
KingbaseES 的:
superuser_reserved_connections就是为了保留一部分超级用户槽位。
但生产还应该额外留:
运维安全空间不要让业务长期顶在:
max_connections 99%官方运维资料给出的 80% 告警参考非常实用。
6.10 风险十:连接多导致内存预算失控
基础连接内存只是第一部分。
真正危险:
每个active连接还可能运行Sort/Hash需要:
work_mem 并行 临时内存所以最大连接数和 SQL 内存参数必须联动。
不能:
连接×10 work_mem仍保持原值然后期待内存安全。
推荐治理模型
建立:
DB连接总预算 ↓ 业务分类预算 ↓ 服务预算 ↓ 实例最大数 ↓ 单实例pool size反向配置。
不要:
每个团队各自定pool 最后数据库承担总和一个简单预算例子
max_connections: 600 超级用户/系统预留: 20 DBA和应急: 30 批处理: 30 应用可用: 520订单:
200会员:
100营销:
60报表:
40剩余:
120作为:
扩容和突发headroom然后再根据:
每类服务最大Pod数算单实例池。
推荐验收顺序
1. SHOW max_connections 2. 统计total/active/idle/idle in transaction 3. 按application_name拆来源 4. 建立各服务实例数 × pool size清单 5. 50/100/200/500并发压测 6. 记录CPU、上下文切换、内存、等待事件 7. 找TPS拐点 8. 把数据库并发限制在拐点附近 9. 在线/批处理分池 10. 验证P95/P99和故障余量回退方案
如果连接池调小以后出现:
应用等待连接过多 吞吐下降 Acquire Timeout不要立即:
直接翻倍先判断:
数据库CPU是否还有余量 active是否低于甜点 SQL是否变慢确认确实池偏小后:
5→8→10小步恢复。
如果数据库已经:
CPU/IO/锁接近上限则应:
限流 优化SQL 拆批 扩容数据库资源而不是放大连接。
最终结论
连接池大小的本质不是:
应用能同时拿多少连接而是:
允许多少工作同时进入数据库竞争有限资源过小:
资源没吃满 应用排队适中:
吞吐最大 尾延迟稳定过大:
CPU调度 上下文切换 内存 IO 锁 WAL一起竞争。
最终表现是:
连接越多 TPS反而越低 P99越来越高如果只记住一句话:
max_connections 决定“数据库最多允许多少连接进来”,连接池决定“应用最多同时把多少工作压给数据库”;性能最优点通常远早于 max_connections 上限。
微服务集群真正成熟的连接治理,应该做到:
数据库有总预算 服务有配额 实例扩容能重新计算 在线和批处理隔离 active并发有上限 idle事务可治理 连接等待可观测这比单纯把:
maximumPoolSize从20改成100,更能真正提高系统吞吐和稳定性。
附录:最低验收门禁
[ ] max_connections已记录 [ ] superuser_reserved_connections已记录 [ ] 总连接/active/idle分别监控 [ ] idle in transaction单独告警 [ ] application_name可识别服务 [ ] 每服务实例数×pool size已登记 [ ] HPA最大实例连接预算已核算 [ ] 50/100/200/500并发曲线已测试 [ ] CPU和上下文切换已记录 [ ] 等待事件已记录 [ ] 内存余量已验证 [ ] 在线和批处理池已隔离 [ ] Acquire Time/P95/P99已监控 [ ] 连接数低于预算门禁 [ ] DBA/应急连接空间已预留 [ ] 回退pool配置已保存转载自:https://blog.csdn.net/u014727709/article/details/164031324
欢迎 👍点赞✍评论⭐收藏,欢迎指正