数据库连接池配置实战:连接太多数据库扛不住,太少应用不够用

去年一个客户的生产环境出现间歇性慢查询,持续了三个月。每次持续时间很短,研发抓不到现场。最后通过监控发现,高峰期获取数据库连接的平均耗时从正常时的5ms飙升到了800ms。应用日志里频繁出现“Timeout waiting for connection”。
检查连接池配置时发现,maximumPoolSize被设成了200,而他们的数据库max_connections只有400。应用有4个实例,每个实例200个连接,理论峰值800,超过数据库上限一倍。连接在连接池里占着,数据库空闲连接并不多,但新请求进来仍然需要等待。
这不是慢查询的问题,是连接池配置错误引发的资源耗尽。
连接池位于应用与数据库之间。配小了,并发请求等着拿连接;配大了,数据库被连接数打满。参数调优不是拍脑袋,需要按业务并发量和数据库规格计算。
01 连接池大小不是越大越好
连接池中的每个连接对应数据库服务端的一个进程或线程。连接越多,数据库的上下文切换开销越大,内存占用也越高。MySQL中每个连接大约消耗256KB到数MB内存,PostgreSQL也类似。200个连接看起来不大,乘上实例数量就可能超限。
正确的做法是从业务并发量反推连接池大小。
假设应用单实例峰值QPS为500,每个请求平均持有连接10ms,那么并发连接数可以通过Little定律估算:500 × 0.01 = 5。加上连接获取等待的冗余,设置15到20已经足够。200远大于这个值,多余连接只会对数据库造成无效负载,不会提升吞吐量。
使用HikariCP时,一个常用的配置基准是:maximumPoolSize设为CPU核心数×2。多数OLTP场景下这个值偏保守,应该按业务并发重新计算。
02 超时配置需要协同一致
连接池的超时参数必须和应用、数据库的超时参数匹配。
connectionTimeout控制应用从连接池获取连接的最大等待时间。设得太短,数据库压力大时请求提前失败;设得太长,故障时大量线程阻塞。通常设在3到5秒,和上游服务的超时对齐。
idleTimeout控制连接池中空闲连接的最大存活时间。设得太短,频繁创建销毁连接增加数据库负载;设得太长,空闲连接占用数据库资源。通常设为10到15分钟,介于数据库的wait_timeout和实例缩容周期之间。
maxLifetime控制连接的最大存活时间,应该比数据库的wait_timeout小几十秒,避免连接被数据库服务端主动关闭时应用还在使用。
03 连接泄漏比慢查询更隐蔽
慢查询影响的是性能,连接泄漏直接导致不可用。
HikariCP提供了leakDetectionThreshold,建议设为connectionTimeout的2到3倍。当连接持有时间超过该阈值时,HikariCP会打印堆栈日志,直接定位到未释放连接的代码位置。
常见的泄漏场景包括:从连接池获取连接后用try-finally确保归还,或者利用try-with-resources自动关闭。使用HikariCP加上leakDetectionThreshold,问题能在几分钟内定位。
04 连接池不是唯一瓶颈
即使应用没有慢查询,如果连接池等待耗时长,整体响应时间也会增加。监控连接池的关键指标包括:
activeConnections: 活跃连接数接近maximumPoolSize时说明连接池已经成为瓶颈。
threadsAwaitingConnection: 等待连接的非零线程数。
connectionTimeoutCount: 获取连接超时的次数。
totalConnections: 总连接数应在maximumPoolSize附近稳定。
如果activeConnections长期接近上限,但数据库CPU利用率不高,需要区分是应用持有连接过久还是连接池真的不够用。前者通过优化事务范围解决,后者适当增加maximumPoolSize。
05 一个真实案例
某SaaS公司,数据库使用PostgreSQL,max_connections设为500。应用部署在Kubernetes上,共20个Pod,每个Pod配置了HikariCP的maximumPoolSize=100。理论总连接数2000,远超数据库上限。
数据库经常报“remaining connection slots reserved for non-replication superuser connections”。分析后发现,夜间大量连接Idle in transaction,由于应用开启了事务但未及时提交或回滚,又未配置idle_in_transaction_session_timeout,空闲事务长期占用连接。
最终采取了三项措施:将HikariCP的maximumPoolSize下调至25;设置idle_in_transaction_session_timeout为30秒;在应用代码中规范事务边界,确保try-catch-finally中始终调用close或rollback。
调整后数据库连接数从峰值600稳定在250左右,应用吞吐量未下降,等待超时告警消失。
写在最后
连接池配置看似简单,maximumPoolSize、connectionTimeout、leakDetectionThreshold几个参数组合起来,决定了应用与数据库之间的通道是否畅通。
那位客户最终将连接池大小从200降到了30,数据库CPU负载下降20%,P99响应时间从150ms降到80ms。运维负责人说:“以前一直以为是SQL慢,查了半年,最后发现是油门踩过头把路堵死了。”
连接池配置不是凭感觉,计算并发、设定监控、验证泄漏,每一个环节都做对了,连接池才能成为加分项而非隐患。你的连接池上限,是基于业务并发算出来的,还是随手填的?