大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
连接池这东西,平时不出问题的时候你根本感觉不到它的存在。一旦出问题------应用连不上数据库了,或者数据库CPU莫名其妙飙到100%------你才会发现,这个小东西的威力有多大。
我见过最典型的一个案例:一个团队把HikariCP的maximumPoolSize从50调到了200,想着"连接多了并发就高了"。结果应用每次重启,200个连接同时涌向数据库,数据库的Threads_connected瞬间冲到上限,正常业务的连接全部被挤掉,整个系统瘫痪了五分钟。
连接池不是"越大越好"。今天把连接池与MySQL的交互彻底拆开讲清楚。
一、连接风暴:三种触发场景
场景一:应用重启后并发建连
应用启动时,连接池会按照minimumIdle配置初始化一批连接。如果minimumIdle设置得比较大(比如100),而且应用是多实例部署(比如10个Pod),那么一次发布重启,就会瞬间产生1000个连接请求涌向MySQL。
MySQL建立连接需要分配线程、初始化会话变量、验证权限。每个连接大约消耗几百微秒到几毫秒。1000个连接同时建连,MySQL的Threads_connected会瞬间飙升,CPU被连接建立操作占满,正常查询的响应时间急剧上升。
解决方案 :控制minimumIdle的大小,不要设置得太高。同时,在应用启动时增加延迟初始化------先初始化少量连接,再通过后台线程逐步补充到minimumIdle。
场景二:连接池参数配置过大
maximumPoolSize设得过大,是另一个常见的连接风暴来源。很多团队觉得"连接多=并发高",把maximumPoolSize设成200甚至500。
但MySQL的连接数是有限制的。max_connections默认是151,调大需要消耗更多内存(每个连接分配独立的排序缓冲区、JOIN缓冲区等)。如果应用连接池设了200,两个应用实例就是400个连接,直接把数据库的max_connections吃满。
解决方案 :maximumPoolSize的计算公式参考:(CPU核心数 × 2) + 磁盘数。对于4核8G的MySQL实例,建议不超过20-30。多实例部署时,总连接数 = 单实例连接池大小 × 实例数,必须小于max_connections。
场景三:长事务占满连接池
连接池的每个连接在使用完毕后会归还给池子。但如果一个事务长时间不提交,这个连接就无法归还。如果长事务频繁出现,连接池中的可用连接会越来越少,最终耗尽。
此时应用会报"Connection is not available, request timed out"错误,但数据库层面看起来"连接数正常"------因为连接都被占用着,但没有活跃查询。
解决方案 :设置maxLifetime(连接最大存活时间)和idleTimeout(空闲超时时间),确保长期不用的连接被回收。同时监控Threads_running和Threads_connected的比值,如果Threads_connected很高但Threads_running很低,说明大量连接处于空闲或等待状态。
二、连接泄漏:最隐蔽的连接池杀手
连接泄漏比连接风暴更隐蔽------它不会瞬间爆发,而是慢慢蚕食数据库的连接资源。
表现 :应用运行几天后,连接数持续上涨,但业务量并没有增长。SHOW PROCESSLIST看到大量Sleep状态的连接,Time值很大。
根因 :代码中获取了连接但没有在finally块中关闭。比如:
bash
Connection conn = dataSource.getConnection();
// 业务逻辑
// 忘记 conn.close()
或者在使用ORM框架时,手动开启了事务但没有提交或回滚。
排查方法:
bash
-- 查看当前连接状态分布
SELECT COMMAND, COUNT(*) FROM information_schema.processlist GROUP BY COMMAND;
-- 查看长时间Sleep的连接
SELECT * FROM information_schema.processlist
WHERE COMMAND = 'Sleep' AND TIME > 300;
-- 对比连接数和活跃线程数
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';
如果Threads_connected远大于Threads_running,且差值持续扩大,基本可以确认存在连接泄漏。
三、HikariCP关键参数实战配置
HikariCP是目前Spring Boot默认的连接池,以下是几个核心参数的实战建议:
| 参数 | 作用 | 建议值 | 注意事项 |
|---|---|---|---|
maximumPoolSize |
最大连接数 | (CPU×2)+磁盘数 | 多实例部署时总连接数<max_connections |
minimumIdle |
最小空闲连接 | 与maximumPoolSize相同 | 避免频繁创建销毁连接 |
connectionTimeout |
获取连接超时 | 3000ms | 太长会导致请求堆积 |
idleTimeout |
空闲连接超时 | 600000ms | 只对minimumIdle之外的连接生效 |
maxLifetime |
连接最大存活 | 1800000ms | 应小于MySQL的wait_timeout |
validationTimeout |
连接有效性检测超时 | 5000ms | 不宜超过connectionTimeout |
一个关键原则 :maxLifetime必须小于MySQL的wait_timeout。MySQL默认wait_timeout是28800秒(8小时),如果maxLifetime设得比它大,连接会被MySQL主动断开,但连接池不知道,拿到这个连接执行查询时会报"Communications link failure"。
四、小结
连接池不是"配大了就好"。连接风暴的三种触发场景------应用重启并发建连、参数配置过大、长事务占满连接池------每一个都可能让数据库瞬间瘫痪。连接泄漏更隐蔽,慢慢蚕食连接资源,直到某一天突然耗尽。排查的核心方法是监控Threads_connected和Threads_running的比值,配置的核心原则是maxLifetime小于wait_timeout。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~