
很多服务的数据库故障,并不是 SQL 变慢才开始的,而是连接池先被打满,请求继续排队,应用线程被占住,最后把局部慢查询放大成整站超时。连接池不是越大越安全,它更像一个阀门:放得太小会限制吞吐,放得太大又会把数据库推到极限。要把它用稳,需要同时设计容量、排队、超时和背压。
连接池解决什么
连接池的价值,是复用已经建立好的数据库连接,减少认证、握手和初始化成本。它不负责让数据库承受无限并发,也不能替慢 SQL、缺索引或长事务兜底。
| 现象 | 常见误判 | 更该检查 |
|---|---|---|
| 请求偶发排队 | 池子太小 | 慢查询、事务时长、并发峰值 |
| 数据库 CPU 飙高 | 再加连接 | 查询计划、索引、批量任务 |
| 应用线程耗尽 | 机器不够 | 等连接时间、超时配置、限流 |
| 发布后连接暴涨 | 正常扩容 | 实例数乘以池上限后的总连接 |
判断连接池是否健康,不能只看“当前连接数”。还要看等待请求数、等待时长、连接持有时长和数据库端活跃查询数。池子满不一定错,长期满且等待时间上涨才危险。
先算总量再分配

连接池参数最容易被写成单个服务的局部配置,例如 max: 50。但数据库看到的是所有应用实例、后台任务、管理脚本和只读副本客户端加起来的总连接数。一个更稳的做法是先从数据库可承受连接数里扣出预留,再按实例数分配。
1 | 单实例池上限 = floor((数据库最大连接数 - 预留连接数) / 应用实例数) |
假设数据库最多承受 300 条连接,预留 60 条给迁移、排障和报表,线上有 12 个应用实例,那么每个实例的连接池上限不应超过 20。后续如果扩容到 24 个实例,却仍保留 max: 20,总连接预算就会翻倍。
连接池下限也要保守。min 设置过高会让低峰期也占住大量连接,多个服务同时启动时还可能制造连接风暴。大多数 Web 服务可以从较小的 min 开始,让流量自然把连接暖起来。
排队要短,超时要分层
连接池满了之后,应用通常会让请求等待空闲连接。这里最怕无限等待:用户已经断开,应用还在排队;上游已经超时,下游还在执行;重试流量又把队列推得更长。
可以把超时拆成三层:
| 层级 | 控制对象 | 建议原则 |
|---|---|---|
| 获取连接超时 | 等池子空位 | 短一些,快速失败 |
| SQL 执行超时 | 单条语句 | 小于接口总预算 |
| 请求总超时 | 用户等待 | 由业务体验决定 |
伪配置可以表达这个关系:
1 | const pool = createPool({ |
获取连接超时不宜比接口超时还长。否则请求会把时间花在队列里,拿到连接时已经没有足够预算执行 SQL。失败也要明确区分:是“拿不到连接”,还是“SQL 执行慢”,这两类问题的处理方式完全不同。
背压要挡在数据库前

当连接池等待队列持续变长,继续接收请求只会增加恢复时间。背压的目标,是在数据库被压垮前让应用主动降速。
常见手段包括:按接口设置并发上限;对昂贵查询做用户级或租户级限流;在只读页面返回缓存结果;暂停低优先级后台任务;队列超过阈值时直接返回可理解的错误。
1 | if (pool.pendingCount > 100 || pool.acquireP95Ms > 150) { |
这类保护逻辑应该优先放在入口层或业务服务层,而不是等数据库拒绝连接后才反应。数据库是共享资源,一条慢路径失控时,不应该拖垮所有读写路径。
指标要能指导动作
只记录错误日志不够。连接池问题通常从延迟分位数和队列长度开始,等到错误率上升时已经偏晚。
| 指标 | 说明 | 可能动作 |
|---|---|---|
| active connections | 正在使用的连接 | 判断是否长期贴近上限 |
| idle connections | 空闲连接 | 评估 min 是否过高 |
| pending requests | 等连接的请求 | 触发背压或扩容分析 |
| acquire latency p95/p99 | 获取连接耗时 | 区分排队和 SQL 慢 |
| query duration p95/p99 | SQL 执行耗时 | 定位慢查询和索引问题 |
| transaction age | 事务持续时间 | 查长事务和锁等待 |
报警也要分层。短时间连接池打满可以只是告警,等待队列持续增长或获取连接超时才需要升级,避免真正的问题被噪音淹没。
落地清单
- 从数据库总连接预算反推每个实例的池上限。
- 扩容应用实例时同步复核连接池总量。
- 获取连接超时要短于接口总超时,并单独记录错误类型。
- 为昂贵接口、后台任务和批量查询设置并发上限。
- 监控等待队列、获取连接耗时、SQL 耗时和事务年龄。
- 压测时覆盖“慢查询 + 高并发 + 重试”的组合场景。
连接池调参不是把 max 改大就结束,而是给数据库入口建立可解释的流量控制。容量决定能放多少请求进去,超时决定多久止损,背压决定什么时候拒绝,指标决定事故发生时从哪里下手。把这几件事一起设计,数据库才更像可治理的共享资源,而不是故障发生时才被发现的瓶颈。