HikariCP连接池调优全流程:80%的人都在犯的三个错,附生产级配置模板
大家好,我是数据库小学妹 👋
上周二凌晨一点,一个朋友打电话过来,说系统全挂了。“白天还好好的,晚上八点多开始接口一个一个地挂。重启也没用,几分钟后又死了。”
我远程连上去看,数据库连接数直接打满。应用端全是Connection pool exhausted的报错。但奇怪的是,当时的并发量跟白天一样,甚至比白天还低一些。
排查了一晚上,发现是连接池的三个配置错误叠加在一起。每个单独看都不致命,凑在一起就出了大事。这个故障也让我注意到一件事:很多团队的连接池配置是照抄网上的模板,或者干脆用默认值,没人专门去调过。
今天把这个故障的排查过程写出来,聊聊连接池调优里那些容易被忽略的细节。
一、连接池不是设个数字就完了
先说我之前也犯过的错。刚转行做数据库那会儿,我觉得连接池很简单,设个最大连接数,设个超时时间,完事了。后来出了事才知道,连接池的参数之间是有关联的,改一个会影响其他参数的表现。
连接池的核心参数可以分成三组。第一组是容量参数,最大连接数和最小空闲数,决定池子有多大。第二组是超时参数,获取连接超时和空闲超时,决定等多久算失败。第三组是生命周期参数,最大生命周期和连接验证,决定连接什么时候换新。
这三组参数必须一起调。只调一组,其他组不管,迟早出问题。我朋友的项目组就是这样,三个错误分别对应这三组。
二、错误一:连接池大小拍脑袋定
这是最普遍的问题。我去看他们的配置:
spring.datasource.hikari:maximum-pool-size:500minimum-idle:50500个连接。我问他们怎么定的这个数,回答是"设大点总没错"。
这是错的。连接池设太大不是资源浪费那么简单。每个数据库连接在MySQL端要占用内存,thread_stack、read_buffer、sort_buffer加起来一个连接大概2到4MB,500个就是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,先别急着改配置,看两个数:
SHOWSTATUSLIKE'Threads_connected';SHOWSTATUSLIKE'Threads_running';Threads_connected是当前连上来的连接总数,Threads_running是正在执行SQL的连接数。前者高后者低,说明大量连接在发呆,大概率是连接泄漏或者空闲回收没生效。两者都高说明有慢查询堆积或者突发流量。
第二步:定位泄漏的代码
如果怀疑连接泄漏,HikariCP有个实用的功能:
spring.datasource.hikari:leak-detection-threshold:60000设了这个值之后,连接被拿走超过60秒没还回来就会打印警告日志,里面包含调用栈,直接定位到哪行代码没关连接。生产环境也建议开着。有一次我们排查了半天的泄漏,开了这个参数5分钟就定位到了问题代码,是一个导出功能里异常分支没关连接。
第三步:看监控面板
用 Prometheus + Grafana 做连接池监控,重点盯四个指标:
| 指标 | 含义 | 异常信号 |
|---|---|---|
| active connections | 正在使用的连接数 | 持续上升不回落 |
| idle connections | 空闲连接数 | 长期为0 |
| total connections | 总连接数 | 触达上限 |
| pending threads | 等待连接的线程 | 大于0说明池不够 |
active持续上升但idle为0就是泄漏。total触达上限但业务流量没涨说明池太小或者连接还不过来。这四个指标比看日志直观,建议提前配好。
避坑清单
做连接池调优,这几个坑提前知道能省不少事。
最大连接数设太大。这是最常见的错。连接池不是越大越好,超过一定数值反而会因为锁争用让性能下降。按公式算,别猜。
超时参数用默认值。HikariCP默认的30秒超时太长了,会让线程池被等待线程占满。改成3秒以内。
不设maxLifetime。连接不限制生命周期,迟早拿到僵尸连接。中间设备的超时时间通常比你想的短。
多实例不预留余量。一个数据库给多个应用用,每个都设满连接数,加起来超过数据库上限。做除法,留20%。
不监控空闲连接回收。只关心活跃连接,不管idle数量。空闲回收策略失效了也不知道,等发现的时候连接池已经被掏空了。这是我踩过的坑,半夜系统突然卡死,翻日志才发现idle已经零了快一周了,没人注意到。
连接池配置对了感觉不到它的存在,配置错了能让你半夜爬起来救火。花半小时把参数理清楚,比出故障再排查划算多了。
你项目的连接池参数是怎么定的?有没有踩过类似的坑?欢迎在评论区聊聊。
我是数据库小学妹,咱们下篇见 👋
本文基于 HikariCP + MySQL 8.0 编写。Druid、c3p0 等连接池的参数名不同,但调优思路完全通用。
