为什么你的数据库连接总超时?99% 的 Java 程序员都踩过这 5 个坑

为什么你的数据库连接总超时?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 个坑填平,你的系统至少能稳一半。

相关推荐
后端不背锅2 小时前
对外接口设计完全指南:安全、高性能、可演进
后端
IT小崔2 小时前
SqlSugar 使用教程
数据库·后端
Oneslide2 小时前
Docker Compose 重启 RabbitMQ 数据丢失?
后端
架构师沉默2 小时前
为什么国外程序员都写独立博客,而国内都在公众号?
java·后端·架构
开心就好20252 小时前
Win11 抓包工具怎么选?网页请求与设备流量抓取
后端·ios
爱丽_3 小时前
Spring 事务:传播行为、失效场景、回滚规则与最佳实践
java·后端·spring
用户3167361303423 小时前
SSE消息推送前后端代码
前端·后端
搬搬砖得了3 小时前
当 GraphQL 变成“全家桶”,Stream 写成“天书”,老板变身“谜语人”:我在代码屎山里的渡劫日常
后端
默海笑3 小时前
Java 基础 12:JavaDoc 生成文档 学习笔记
后端