大家好我是数据库小学妹 上周二凌晨一点一个朋友打电话过来说系统全挂了。“白天还好好的晚上八点多开始接口一个一个地挂。重启也没用几分钟后又死了。”我远程连上去看数据库连接数直接打满。应用端全是Connection pool exhausted的报错。但奇怪的是当时的并发量跟白天一样甚至比白天还低一些。排查了一晚上发现是连接池的三个配置错误叠加在一起。每个单独看都不致命凑在一起就出了大事。这个故障也让我注意到一件事很多团队的连接池配置是照抄网上的模板或者干脆用默认值没人专门去调过。今天把这个故障的排查过程写出来聊聊连接池调优里那些容易被忽略的细节。一、连接池不是设个数字就完了先说我之前也犯过的错。刚转行做数据库那会儿我觉得连接池很简单设个最大连接数设个超时时间完事了。后来出了事才知道连接池的参数之间是有关联的改一个会影响其他参数的表现。连接池的核心参数可以分成三组。第一组是容量参数最大连接数和最小空闲数决定池子有多大。第二组是超时参数获取连接超时和空闲超时决定等多久算失败。第三组是生命周期参数最大生命周期和连接验证决定连接什么时候换新。这三组参数必须一起调。只调一组其他组不管迟早出问题。我朋友的项目组就是这样三个错误分别对应这三组。二、错误一连接池大小拍脑袋定这是最普遍的问题。我去看他们的配置spring.datasource.hikari:maximum-pool-size:500minimum-idle:50500个连接。我问他们怎么定的这个数回答是设大点总没错。这是错的。连接池设太大不是资源浪费那么简单。每个数据库连接在MySQL端要占用内存thread_stack、read_buffer、sort_buffer加起来一个连接大概2到4MB500个就是1到2GB还不算连接切换时的上下文开销。更关键的是连接太多会引发CPU锁争用。MySQL内部用互斥锁保护共享数据结构连接越多锁冲突越频繁。我见过一个案例连接池从200降到30之后QPS反而提升了40%就是因为锁冲突大幅减少了。HikariCP官方推荐公式是连接池大小 CPU核数 × 2 磁盘数这个公式适用于OLTP场景就是那种短查询、高并发的业务。8核CPU、单磁盘的服务器连接池设17左右就够了。如果有大量长查询或者存储过程可以适当放大到3到5倍但别超过100。超过100的连接池99%的情况都是配错了。还有个容易被忽略的点是多应用实例。如果一台数据库服务器要给5个应用实例用每个实例的连接池大小应该是(数据库max_connections × 0.8) / 实例数留20%的余量给运维操作和意外情况。我给朋友算了一下他们的数据库max_connections是500有3个应用实例每个实例的连接池不应该超过130。但实际业务是轻量的OLTP按公式算17就够。最后我们设了25留了一点余量。三、错误二获取连接超时不设或乱设这个更隐蔽。连接池都有个参数叫connectionTimeout获取连接超时意思是当池里没有可用连接时线程最多等多久超过这个时间还拿不到就抛异常。我朋友的配置里这个值是默认30秒。30秒意味着什么假设连接池满了一个新请求过来会等30秒。在这30秒里线程是挂起的不释放也不报错。如果这时候有大量请求涌入比如一次营销活动或者定时任务触发几百个线程同时等连接每个都等30秒应用服务器的线程池会被这些等待线程占满新请求连应用层都进不来直接超时返回。等30秒到了线程释放请求失败前端重试又送来一批新请求。恶性循环。获取连接超时不应该超过5秒。HikariCP默认30秒太保守了。我的建议是一般业务1到3秒核心链路1秒以内批处理任务可以适当放宽但别超过10秒。超过3秒还没拿到连接说明池子真的不够用了再等也等不来不如快速失败。快速失败还有个好处是触发熔断让降级逻辑有机会介入而不是让线程干等30秒把整个线程池拖死。spring.datasource.hikari:connection-timeout:3000# 3秒快速失败配合应用的熔断器一起用拿不到连接就降级或者返回缓存比直接挂掉强得多。四、错误三忽略连接的最大生命周期这个最难发现。maxLifetime控制一个连接从创建到强制关闭的时间。我朋友的配置里这个值设了0意思是不限制生命周期连接可以一直活着。听起来挺好其实是个大坑。数据库和应用之间通常有防火墙、负载均衡、NAT网关。这些中间设备有个共同特点会掐断长时间没有数据传输的TCP连接。比如防火墙的空闲超时通常是15到30分钟连接过了30分钟没有流量就直接断开。但应用侧不知道连接池还以为连接是好的下次拿出来用发SQL才发现断了。这种情况在高可用场景里更常见。数据库主从切换、VIP漂移、连接代理重启老连接全部失效连接池不知道。我自己也栽在这个问题上。线上偶尔会报Communications link failure频率很低一天就一两次。我查了很久以为是网络抖动后来发现是连接生命周期没设。防火墙隔几个小时清一次空闲连接池里正好有连接被清掉了下次用到就报错。设了maxLifetime之后这个问题再没出现过。经验值是30分钟spring.datasource.hikari:max-lifetime:1800000# 30分钟别太短太短的话连接频繁销毁重建开销反而大。也别太长超过防火墙的超时时间就没意义了。不知道防火墙超时时间的话先设30分钟观察日志里的连接错误频率再微调。五、一份经过验证的 HikariCP 配置排查完故障我给朋友重新写了配置spring.datasource.hikari:# 连接池大小按公式算maximum-pool-size:25# 最小空闲连接minimum-idle:5# 获取连接超时3秒connection-timeout:3000# 空闲连接30分钟回收idle-timeout:1800000# 连接最大生命周期30分钟max-lifetime:1800000# 连接测试connection-test-query:SELECT 1# 连接池名称方便日志排查pool-name:myAppPool# 泄漏检测60秒未归还打印警告leak-detection-threshold:60000这份配置适用于大多数中等规模的OLTP应用。几个参数的选择逻辑maximum-pool-size: 25——8核CPU按公式算17留余量设25minimum-idle: 5——保证低峰期有连接可用connection-timeout: 3000——3秒快速失败idle-timeout: 1800000——30分钟回收空闲连接max-lifetime: 1800000——30分钟强制换新leak-detection-threshold: 60000——60秒未归还就报警用Druid或者c3p0的话参数名不同调优思路通用。六、连接池故障的排查思路连接池配错了已经配了出了问题怎么快速排查我习惯分三步。第一步确认是不是连接池的问题看到Too many connections或者Connection pool exhausted先别急着改配置看两个数SHOWSTATUSLIKEThreads_connected;SHOWSTATUSLIKEThreads_running;Threads_connected是当前连上来的连接总数Threads_running是正在执行SQL的连接数。前者高后者低说明大量连接在发呆大概率是连接泄漏或者空闲回收没生效。两者都高说明有慢查询堆积或者突发流量。第二步定位泄漏的代码如果怀疑连接泄漏HikariCP有个实用的功能spring.datasource.hikari:leak-detection-threshold:60000设了这个值之后连接被拿走超过60秒没还回来就会打印警告日志里面包含调用栈直接定位到哪行代码没关连接。生产环境也建议开着。有一次我们排查了半天的泄漏开了这个参数5分钟就定位到了问题代码是一个导出功能里异常分支没关连接。第三步看监控面板用 Prometheus Grafana 做连接池监控重点盯四个指标指标含义异常信号active connections正在使用的连接数持续上升不回落idle connections空闲连接数长期为0total connections总连接数触达上限pending threads等待连接的线程大于0说明池不够active持续上升但idle为0就是泄漏。total触达上限但业务流量没涨说明池太小或者连接还不过来。这四个指标比看日志直观建议提前配好。避坑清单做连接池调优这几个坑提前知道能省不少事。最大连接数设太大。这是最常见的错。连接池不是越大越好超过一定数值反而会因为锁争用让性能下降。按公式算别猜。超时参数用默认值。HikariCP默认的30秒超时太长了会让线程池被等待线程占满。改成3秒以内。不设maxLifetime。连接不限制生命周期迟早拿到僵尸连接。中间设备的超时时间通常比你想的短。多实例不预留余量。一个数据库给多个应用用每个都设满连接数加起来超过数据库上限。做除法留20%。不监控空闲连接回收。只关心活跃连接不管idle数量。空闲回收策略失效了也不知道等发现的时候连接池已经被掏空了。这是我踩过的坑半夜系统突然卡死翻日志才发现idle已经零了快一周了没人注意到。连接池配置对了感觉不到它的存在配置错了能让你半夜爬起来救火。花半小时把参数理清楚比出故障再排查划算多了。你项目的连接池参数是怎么定的有没有踩过类似的坑欢迎在评论区聊聊。我是数据库小学妹咱们下篇见 本文基于 HikariCP MySQL 8.0 编写。Druid、c3p0 等连接池的参数名不同但调优思路完全通用。