跳到主要内容
约 10 分钟5 张卡

数据库连接池

数据库连接池复用物理连接,并限制应用同时占用数据库的连接数量。本文以 HikariCP 为例,从连接的借出与归还讲起。接着说明怎样确定容量、分配超时预算,再介绍泄漏排查和池内部的性能机制。容量选择要同时考虑应用并发与数据库承载能力。

1. 面试核心​

先记住这几条,再读正文
  • 连接复用。 连接池复用物理连接,业务调用代理连接的 close() 时将连接归还池中。卡 · 代理连接与资源归还
  • 总连接容量。 maximumPoolSize 限制单池连接总数,多实例容量按各池上限之和计算。卡 · 容量验证指标
  • 等待与执行超时。 connectionTimeout 限制获取连接的等待,Statement.setQueryTimeout 限制 SQL 执行时间。卡 · 超时预算
  • 轮换与保活。 maxLifetime 控制连接轮换,使用中的到期连接归还后才淘汰,keepaliveTime 只对空闲连接执行保活检查。
  • 泄漏检测。 leakDetectionThreshold 在连接借出超时后记录借用堆栈,连接仍由业务或事务管理器归还。卡 · 连接泄漏检测

2. 代理连接与资源归还​

业务用完连接后,需要把它归还,供下次请求复用。手动通过 DataSource 借连接时,可以用 try-with-resources 保证异常路径也执行关闭:

try (Connection connection = dataSource.getConnection()) {
// execute SQL
}

HikariCP 返回的是包装物理连接的代理 Connection。离开代码块时,try-with-resources 会调用它的 close()。代理会重置必要状态,再把可复用的物理连接放回池中,底层 TCP 连接继续保留。

使用 Spring、JdbcTemplate 或 MyBatis-Spring 时,应让框架按事务边界获取和释放连接。事务内的操作要复用框架绑定的连接,避免绕过它另借连接。

DataSource 和连接池应作为长生命周期组件管理。每次请求新建一个池,会失去复用能力。并发请求还会因此创建大量物理连接。

3. 连接池容量​

HikariCP 的 maximumPoolSize 包含空闲和正在使用的连接。当池已达到上限且没有可用连接时,新的 getConnection() 会等待。等待超过 connectionTimeout 后,它会抛出异常。

确定池大小时,需要同时核对数据库容量、单实例负载和实例数量:

  • 确认数据库允许多少连接,以及能高效处理多少并发连接。
  • 确认单实例数据库操作的并发量,以及每次持有连接的时间。
  • 统计共享同一数据库的应用实例数量。

例如,20 个实例各设 100,连接总上限就是 2000。数据库仍受锁、I/O、CPU 和内存限制,所以连接总数增加后,吞吐未必增加。

4. 容量验证指标​

配置一个初始容量后,需要用压测和生产指标判断它是否合适。重点观察连接等待与数据库负载:

  • 记录 active、idle 和 pending。它们分别表示使用中的连接数、空闲连接数和等待连接的线程数。
  • 记录获取连接的等待时间与超时数。
  • 记录 SQL 执行和事务持有时间。
  • 观察数据库 CPU、I/O、锁等待及最大连接使用量。
  • 统计应用实例扩缩容后的总连接数。

active 长期达到上限,而数据库仍有余量时,可以小步增加容量,再比较等待时间与吞吐。若数据库已经饱和,扩池会让更多慢查询同时进入数据库,加剧争用。这时应优化查询、缩短事务,或限制上游并发。

5. 超时预算​

一个请求会经过等待连接、执行 SQL 和结束事务等阶段。配置超时前,需要区分四个时间范围:

  • connectionTimeout:它限制从池中等待可用连接的最长时间。
  • JDBC 查询超时:它限制一条语句的执行时间。
  • 事务超时:它限制整个数据库工作单元的持续时间。
  • 请求超时:它限制调用方等待整个接口的时间。

各阶段预算应从请求剩余时间分配,并为回滚和释放资源预留时间。仅把几项配置写成依次递减的数字,无法保证请求按时结束。一次事务内多条 SQL 的时间会累积,重试也要消耗同一总预算。若只设置连接获取超时,未结束的慢 SQL 仍会占用连接。还需要设置查询与事务超时。

6. 连接生命周期​

连接池会长期复用连接,也需要定期替换老连接。maxLifetime 限制物理连接的使用寿命。数据库、代理或网络若有连接寿命上限,该值应比上限略短。使用中的连接不会在执行期间被强行关闭,归还后才会淘汰。

keepaliveTime 控制空闲连接的保活检查间隔,必须短于 maxLifetime。保活通过轻量检查确认连接仍然可用。JDBC 4 驱动优先使用 Connection.isValid(),只有驱动需要时才配置验证 SQL。

minimumIdle 未单独设置时,HikariCP 默认将它设为 maximumPoolSize。这会形成固定容量池。动态缩减是否有价值,要根据资源成本和流量模式决定。

7. 连接泄漏检测​

连接长时间未归还时,需要找出它被哪段工作占用。常见的占用原因包括:

  • 应用在异常路径上没有调用 close。
  • 事务包含远程调用、文件处理或长时间计算。
  • 流式 ResultSet 尚未关闭。
  • 异步任务或线程切换破坏了资源边界。
  • 数据库阻塞让连接看起来长期未归还。

HikariCP 用 leakDetectionThreshold 检测疑似泄漏。连接离池超过该阈值时,HikariCP 会记录借用堆栈。告警只记录线索,不会强行回收正在使用的连接。长事务和慢 SQL 超过同一阈值时也会触发告警,所以还要检查工作结束后是否归还。

8. 连接池耗尽的排查顺序​

连接池耗尽说明可用连接已经用完,原因还要结合应用和数据库判断。排查应沿着等待、占用和归还这条线展开:

  1. 确认 pending、active 和获取超时是否同步上升。
  2. 查看占用连接最长的调用栈、事务和 SQL。
  3. 在数据库侧检查运行语句、锁等待和连接状态。
  4. 判断是连接未归还、查询变慢,还是流量超过容量。
  5. 修复根因后重新压测池大小和超时,不直接永久扩容。

9. 容量估算与多连接占用​

稳定负载下,可粗略估算平均同时借出的连接数。估算公式为“每秒借用次数 × 平均持有秒数”。例如每秒 400 次借用,每次 50ms,平均约占用 20 个连接。20 是平均占用量,池容量还需为峰值和少数耗时特别长的请求预留余量。最终容量也要满足等待时间目标,并控制在数据库能承受的范围内。

多实例部署要计算所有池的总上限。还要为管理、迁移和其他服务预留连接。即使单实例配置不变,扩容应用也会放大总连接数。因此,扩容时需同步核对数据库容量。

请求持有连接再申请第二个连接,会增加池内资源等待的风险。例如外层事务挂起,但仍占有连接。内层 REQUIRES_NEW 又申请新连接。当池中连接都被这类外层事务占用时,内层事务只能等待。即使数据库没有锁冲突,请求也会卡在这里。因此,应优先减少嵌套持有,再评估最坏情况下同时需要多少连接。池大小仍要服从数据库容量。

10. 超时取消与连接轮换​

设置了超时,还需要确认超时后数据库是否真正停止工作。connectionTimeout 只管理取连接等待,借到连接后的 SQL 由查询超时管理。

JDBC 通过 Statement.setQueryTimeout 限制语句执行时间。验证取消行为时,应检查驱动与数据库的响应:

  • 驱动用什么方式取消查询。
  • 网络异常发生后,调用需要多久才能返回。
  • 数据库是否终止了对应 SQL。

事务超时还取决于事务管理器如何把剩余时间传递给语句。

例如请求总预算为 1s,连接等待已经用了 300ms。这时,执行、回滚、释放和响应合计只剩 700ms。若给每条 SQL 再分配 900ms,就会超过请求总预算。外层 HTTP 超时后,查询仍需由线程或驱动取消。若查询没有真正取消,连接仍处于占用状态。

maxLifetime 管理连接轮换;使用中的连接会等到归还后再淘汰。因此,事务执行时间需要单独限制。即使 maxLifetime 较短,数据库或代理仍会按自身时限断开一条无限长的事务连接。处理空闲连接超时,应结合 keepalive、池的空闲管理与基础设施策略。不要把所有超时都折算成一个连接寿命值。

10.1 HikariCP 的内部性能机制​

每次借还连接都会经过池内部的管理代码。HikariCP 通过优化连接交接、代理调用和集合操作,减少这部分高频开销:

  • ConcurrentBag:它优先从线程本地的 ThreadLocal 缓存寻找可复用连接。找不到时,再通过共享集合和跨线程交接获取连接,并直接服务等待者。这样能减少竞争与交接延迟。借出状态通过 CAS 协调,也就是只有状态仍等于预期值时才更新,以避免同一连接被重复借出。
  • 字节码与 JIT 内联:内联指编译器把被调用方法展开到调用处,省去方法调用开销。HikariCP 缩短热点方法,并简化继承层次。这能减少类型转换和调用开销,使关键路径更适合 JIT 内联。
  • FastList:连接代理用它记录打开的 Statement。内部逻辑已保证下标有效,因此它去掉了下标范围检查。删除时,它从尾部开始查找。后创建的 Statement 通常先关闭,这样更容易找到目标。

这些优化降低池自身借还与代理管理的开销;端到端延迟仍取决于 SQL、锁等待、网络和连接持有时间。比较性能时,应先固定驱动、池容量和负载,再比较等待时间与吞吐。

11 · 面试题卡片​

11.1 怎样设计和配置数据库连接池​

出现公司:招银网络科技

考察重点:说明物理连接怎样复用,以及代理 close() 怎样归还连接。能结合数据库容量和请求预算配置连接池,并区分泄漏与慢查询。

相关内容:代理连接与资源归还、连接池容量;超时取消与连接轮换、连接池耗尽的排查顺序。

参考回答

连接池通过复用有限数量的物理连接,减少建连成本并限制数据库并发。

业务拿到代理 Connection,调用 close() 时把连接归还池中。异常路径同样要完成归还,框架管理的连接则按事务边界释放。

maximumPoolSize 限制单池连接总数。连接都被占用时,新请求最多等待 connectionTimeout。容量要结合实例数、持有时间和数据库承载能力压测。数据库已经饱和时,继续增加连接会加剧争用。

查询、事务和请求也各有超时范围,需要按剩余预算分配,并验证实际取消行为。maxLifetime 应略短于基础设施的连接寿命上限。池耗尽时,要把 active、pending 和获取等待与慢 SQL、锁等待关联起来。泄漏堆栈只是借出超时的线索,长事务也会触发。

11.2 连接等待变长时,怎样判断应该扩池还是减少数据库并发?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:说明借用率和持有时间怎样影响连接占用量。能结合数据库饱和程度、多实例总量和嵌套借用判断是否扩池。

相关内容:容量验证指标、容量估算与多连接占用。

参考回答

数据库仍有余量且池已满时,可以小步压测扩池;数据库已饱和时,应减少并发占用。

先查看 active、pending 与获取连接的延迟,再对照数据库 CPU、I/O 和锁等待。接着查看事务持有连接的时间,判断等待来自容量限制还是查询变慢。数据库饱和时,应优化 SQL、缩短事务或限制上游并发。

每秒借用次数乘以平均持有秒数,可以估计平均占用量。容量还要为峰值预留余量,并计入多实例总连接数。请求持有一条连接再申请第二条时,还要检查是否出现池内资源等待。

11.3 设置 connectionTimeout 后,为什么接口仍可能卡很久?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:区分等待连接与执行查询的时间范围,说明剩余预算怎样分配。理解驱动取消查询后,连接何时才能释放。

相关内容:超时预算、超时取消与连接轮换。

参考回答

connectionTimeout=300 只限制等待连接最多 300ms,借到连接后执行 SQL 的时间另算。

如果 SQL 卡在锁等待或网络读取,连接仍会被占用,接口也就继续等待。因此,还要分别配置并验证查询、事务、网络和请求超时。各阶段都按请求剩余时间分配预算,并把重试耗时计入总预算。

外层请求超时后,应检查驱动是否真正取消 SQL,以及连接何时释放。只有内部工作结束并归还连接,其他请求才能使用它。

11.4 连接池耗尽且出现泄漏告警,怎样确认是泄漏还是慢查询?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:说明泄漏阈值和借用堆栈能提供什么证据。能关联数据库阻塞与应用执行,确认连接最终是否归还。

相关内容:连接泄漏检测、连接池耗尽的排查顺序。

参考回答

要结合借用堆栈、数据库执行状态和最终归还情况判断,泄漏告警本身只说明借出时间超过阈值。

沿借用栈检查异常路径是否执行了 close(),再关联事务、正在执行的 SQL 和数据库锁等待。事务中若有远程调用,也要确认连接是否一直被它占用。

工作结束后能正常归还,且耗时主要来自慢 SQL 或锁等待时,应按慢事务处理。若代码已经失去连接引用却没有归还,就有明确的泄漏证据。泄漏检测只记录线索,最终仍需修复 close() 或事务结束路径。单独降低 maxLifetime 或扩大池容量,都无法补上缺失的归还操作。

11.5 HikariCP 为什么有较低的连接池开销?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:解释连接交接、代理调用和专用集合分别节省了什么开销。

相关内容:HikariCP 的内部性能机制。

参考回答

HikariCP 通过优化连接交接、代理调用和 Statement 跟踪,降低池自身的管理开销。

ConcurrentBag 优先复用线程本地缓存中的连接,并直接向等待者交接连接,减少竞争。较短的热点方法有利于 JIT 内联,减少代理调用开销。FastList 用于跟踪打开的 Statement,省去多余的下标检查,并从尾部查找待删除对象。

这些优化针对借还连接的过程。SQL 执行和锁等待仍需要从数据库侧优化,比较连接池性能时应固定驱动、容量和负载。

12. 参考资料​