线上系统突然大量请求超时,日志里出现“获取数据库连接超时”,很多人第一反应是把连接池调大。这个动作有时能缓解几分钟,但经常会把数据库压得更惨。连接池耗尽不是一个单独问题,它通常是慢 SQL、长事务、连接泄漏、线程池堆积和下游等待一起作用后的结果。
面试里如果被问“数据库连接池满了怎么办”,不要只回答“看慢查询”。慢查询很重要,但它可能只是最后浮出来的症状。更关键的问题是:连接从池里借出去以后,到底被谁拿着,拿了多久,为什么没有及时还回来。
连接池的本质是有限资源排队
应用访问数据库时,不会每次都重新建立物理连接,而是从连接池借一个连接,用完归还。池子太小,高峰时请求会排队;池子太大,数据库要同时处理过多连接,CPU、内存、锁竞争和上下文切换都会变差。
所以连接池不是越大越好。它应该和应用线程池、数据库承载能力、接口耗时和业务峰值一起估算。一个常见事故是:应用线程池开得很大,所有线程都在等数据库连接,连接池又把压力传给数据库,最后请求排队、超时、重试同时发生。
长事务会悄悄占住连接
很多连接池问题不是单条 SQL 极慢,而是事务范围过大。比如在事务里做远程调用、等待消息返回、写文件、跑复杂规则,连接会在整个事务期间被占用。即使每条 SQL 都不算慢,连接借出时间也会很长。
后端代码里尤其要警惕“为了方便”把一个大方法整体加事务。事务应该包住必须原子提交的数据库操作,不应该覆盖无关的网络等待。否则下游系统慢一点,数据库连接也被一起拖住。
泄漏要看借出和归还
连接泄漏不一定是开发者手写 JDBC 忘了 close。ORM、事务注解、异常捕获、异步线程切换都可能让连接生命周期变得复杂。比如事务方法里吞掉异常,或者把数据库访问放进不受事务管理的异步回调,都会制造难查的边界问题。
排查时可以看连接池的 active、idle、pending、acquire timeout 等指标,配合应用线程栈和数据库会话信息。线程栈能告诉你应用卡在哪里,数据库会话能告诉你连接正在执行 SQL、等待锁,还是空闲但没归还。
调大连接池之前先问三个问题
第一,数据库是否还有余量。如果数据库 CPU 和锁等待已经很高,扩大连接池只会让更多请求挤进去。
第二,连接持有时间为什么变长。是 SQL 慢、锁等待、事务太大,还是应用在事务里等下游。
第三,请求超时和重试是否加剧了拥塞。用户侧超时后如果上游继续重试,后端可能同时处理多份相同压力,连接池会更快被打满。
更可靠的处理方式
短期止血可以限流、降低重试、隔离非核心接口、临时扩容应用或数据库资源。根因修复则要缩短事务、优化 SQL 和索引、补上连接泄漏检测、拆分慢任务、把外部调用移出事务边界。
面试里的高质量回答应当围绕资源生命周期:连接池耗尽不是“连接不够”四个字,而是连接被有限线程借出后没有及时归还。能把线程池、事务、SQL、下游和重试串起来,才是真正的后端排查能力。