为什么你的数据库连接总超时?99% 的 Java 程序员都踩过这 5 个坑
做后端开发这么多年,我见过最让程序员崩溃的场景,不是代码逻辑写错,而是数据库连接池被打爆------系统直接宕机,老板在群里疯狂 at 你。
今天这篇文章,是我去年处理了 20+ 次连接池故障后的血泪总结。我把最常见的 5 个坑整理出来,看看你踩过几个?
坑 1:连接池大小"随手配"
很多人配连接池就是抄网上的:
yaml
spring:
datasource:
hikari:
maximum-pool-size: 20
20?50?100?这个数字是怎么来的?大部分人是"感觉差不多就行"。
真实场景是这样的:
假设你的数据库最大连接数是 200,你的服务部署了 5 个实例,每个实例配了 maximum-pool-size=50。那么理论最大连接数是 250,已经超过数据库上限了。
正确的做法是:
yaml
spring:
datasource:
hikari:
# 经验公式:(核心线程数 * 2) + 磁盘 IO 线程数
# 一般建议:CPU 核心数 * 2 + 磁盘数
maximum-pool-size: 20
# 最小空闲连接数
minimum-idle: 5
坑 2:连接泄漏了,还不知道
什么叫连接泄漏?就是从连接池拿了一个连接,用完了没还回去。
一个连接泄漏了不可怕,可怕的是你没发现它泄漏了,然后一个接一个,直到池子被掏空。
怎么快速定位泄漏?
HikariCP 自带了泄漏检测:
yaml
spring:
datasource:
hikari:
leak-detection-threshold: 60000
设置为 60000 毫秒后,如果一个连接被借出去超过 60 秒没还,HikariCP 会打一条日志:
text
Long connection leak detected, elapsed time=62134ms, thread=pool-1-thread-3, ...
别小看这条日志,它就是你排查泄漏的起点。
坑 3:把连接池当队列用
我见过最离谱的代码是这样的:
java
List<Connection> connections = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Connection conn = dataSource.getConnection();
connections.add(conn);
}
一次从池子里拿 10 个连接,业务处理完再逐个还回去------相当于你一个人占着 10 辆共享单车,其他人都没车骑。
正确姿势:
java
for (Order order : orders) {
try (Connection conn = dataSource.getConnection()) {
processOrder(conn, order);
}
}
坑 4:慢 SQL 把连接卡死
假设你的连接池大小是 20,如果有一条 SQL 执行了 5 分钟,它就会占用一个连接 5 分钟。在这 5 分钟内,如果有 20 个请求进来,池子就满了。
更可怕的是,如果数据库连接超时设置是 30 秒,而你的慢 SQL 执行了 5 分钟,那么这条 SQL 可能引发重试,最终让同一个慢 SQL 同时占用多个连接。
解决方案:
yaml
spring:
datasource:
hikari:
connection-timeout: 30000
validation-timeout: 5000
max-lifetime: 1800000
同时,务必加上慢 SQL 监控:
java
@Aspect
@Component
public class SlowSqlInterceptor {
@Around("execution(* javax.sql.DataSource.getConnection())")
public Object monitorConnection(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
if (cost > 1000) {
log.warn("获取连接慢!耗时={}ms, stack={}", cost,
Arrays.toString(new Throwable().getStackTrace()));
}
}
}
}
坑 5:忽略了数据库端的连接限制
很多人只盯着应用层配置,忘了数据库本身也有连接数限制。
MySQL 默认 max_connections 是 151,PostgreSQL 默认是 100。如果你的应用连接池配得太大,数据库会直接拒绝连接。
上线前必查:
sql
-- MySQL 查看当前最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- PostgreSQL 查看
SHOW max_connections;
-- 查看当前实际连接数
SHOW STATUS LIKE 'Threads_connected';
如果数据库连接数经常逼近上限,要么扩容数据库,要么拆分业务------别无他法。
总结
今天总结了连接池的 5 个经典坑:
| 坑 | 症状 | 解法 |
|---|---|---|
| 池大小乱配 | 连接不够用或超限 | 按容量统一规划实例总连接数 |
| 连接泄漏 | 池子逐渐变小 | 开启 leak-detection-threshold |
| 攒着连接不用 | 池子瞬间耗尽 | 用完立即归还 |
| 慢 SQL 卡池 | 请求堆积超时 | 加超时控制和监控 |
| 忽略数据库限制 | 数据库直接拒绝 | 上线前检查 max_connections |
数据库连接池的问题,说到底就是两个:拿得到 和 用得快。把这 5 个坑填平,你的系统至少能稳一半。