纯概念, 目前暂未看过源码
为什么使用数据库连接池
传统方式的缺点:
-
资源消耗大:创建数据库连接是一个非常"昂贵"的操作。它需要经历TCP三次握手、数据库权限验证、连接状态初始化等,消耗大量的CPU和内存资源。
-
响应速度慢:每次请求都要等待连接建立,导致整体响应时间变长。
-
难以管理连接数:如果并发请求量很大,可能会瞬间创建大量连接,耗尽数据库资源,导致数据库崩溃。
连接池的解决方案:
连接池的核心思想是空间换时间。在系统初始化时,就预先创建一定数量的数据库连接并放入一个"池子"中管理。当应用程序需要时,直接从池中获取一个空闲连接,使用完毕后归还给池子,而不是真正关闭它。
优点:
-
减少连接创建开销:连接的初始化工作只在池子启动时完成一次,后续请求直接复用,极大提升了性能。
-
控制资源使用:池子可以限制最大连接数,防止数据库过载。
-
统一连接管理:池子可以管理连接的生命周期,包括检测无效连接、自动重连等。
数据库连接池实现过程
DB连接池中核心组件
- 线程安全的并发队列 (如
BlockingQueue
)来存放空闲的数据库连接
-
空闲连接队列 (
idleConnections
):存放当前可用、未被使用的连接。 -
活跃连接集合 (
activeConnections
):存放正在被应用程序使用的连接(用于监控和管理)。
工作流程
1.链接初始化
在应用程序启动或连接池首次被使用时,根据配置的initialSize
(初始连接数),创建一定数量的数据库连接,并将它们放入空闲连接队列。
- 获取连接阶段
当应用程序调用 DataSource.getConnection()
时:
-
检查空闲队列 :首先尝试从
idleConnections
队列中取出一个连接。 -
创建新连接(如果需要):
-
如果空闲队列为空,且当前总连接数(活跃+空闲)小于
maximumPoolSize
(最大连接数),池子会创建一个新的物理连接。 -
如果总连接数已达上限,请求会等待(在
maxWaitTime
时间内),直到有其他连接被归还。
-
-
验证连接有效性 :从池中取出的连接,在交给应用程序之前,可能会用一条简单的SQL(如
SELECT 1
)进行有效性验证(取决于testOnBorrow
配置),确保连接没有被数据库端意外关闭。 -
标记为活跃 :将连接从
idleConnections
移到activeConnections
,然后返回给应用程序。 -
使用连接阶段
应用程序拿到连接后,像普通连接一样执行SQL操作。但它实际上是通过代理完成SQL操作。
- 归还连接阶段
当应用程序调用 Connection.close()
时:
-
代理拦截 :代理连接的
close()
方法被调用,它并不会真正关闭底层的物理连接。 -
状态重置 :连接池会回滚可能未提交的事务、重置连接的相关配置(如
autoCommit
状态)。 -
回归池子 :将该物理连接从
activeConnections
集合中移除,重新放回idleConnections
队列,等待下一次被获取。
其他重要管理机制
-
空闲连接回收 :有一个后台线程定期扫描
idleConnections
队列。如果某个连接的闲置时间超过了idleTimeout
,则将其真正关闭,以节省数据库资源。 -
失效连接检测 :后台线程会定期检查池中的连接是否仍然有效(例如,通过执行
SELECT 1
),如果连接由于网络问题或数据库重启而失效,则将其丢弃并补充新的连接。 -
连接泄漏回收 :监控那些被应用程序借出但长时间未归还的连接(通过
leakDetectionThreshold
配置)。如果超过阈值,连接池可能会记录警告日志甚至强制回收该连接。
工作流程

数据库连接池和线程池关系
两者"池化"思想上是相通的,但由于它们管理的资源性质完全不同,因此在参数配置的侧重点和考量维度上存在显著差异。
-
线程池 :管理的是 CPU 计算资源。线程是 JVM 层面的执行单元,其创建和销毁消耗的是 CPU 和内存。
-
数据库连接池 :管理的是 外部稀缺资源。数据库连接背后是网络连接、数据库服务器本身的会话、内存和CPU。数据库能同时支撑的连接数是硬性限制,远比应用服务器的线程数要少。
参数类别 | 线程池 (如 ThreadPoolExecutor ) |
数据库连接池 (如 HikariCP, Druid) | 核心差异说明 |
---|---|---|---|
核心池大小 | corePoolSize |
minimumIdle (如 HikariCP) |
思路相似 :希望保持的常驻资源数量。但线程池常基于CPU核心数,连接池常设一个较小值以节省DB资源。 |
最大池大小 | maximumPoolSize |
maximumPoolSize |
差异巨大 : • 线程池 :可达数百甚至数千,取决于业务类型和服务器性能。 • 连接池 :通常很小 (如 20-100),受限于数据库的max_connections 。这是最关键的区别。 |
空闲资源处理 | keepAliveTime + unit |
idleTimeout / maxLifetime |
目标不同 : • 线程池 :回收超出核心数 的空闲线程,快速释放内存。 • 连接池 :回收任何空闲 连接(防空闲占用),并有最大生命周期(强制刷新,防止网络僵死)。 |
任务/连接队列 | workQueue (如 LinkedBlockingQueue ) |
通常无显式配置,内部实现 | 角色不同 : • 线程池队列 :缓冲待执行的任务 。队列大小直接影响任务提交策略(拒绝 or 等待)。 • 连接池 :没有"任务队列",只有空闲连接的物理队列 。获取连接的等待行为由connectionTimeout 控制。 |
资源创建/销毁 | ThreadFactory |
无直接对应参数,由驱动完成 | • 线程池 :可自定义线程创建方式(如线程名、优先级)。 • 连接池:连接创建由JDBC驱动和数据库协议决定,池只负责调用。 |
资源验证 | 无此概念 | connectionTestQuery validationTimeout |
连接池特有 :由于连接是网络资源,可能因网络问题或数据库重启而失效,因此必须 有有效性检查机制(如SELECT 1 )。线程是本地资源,无需验证。 |
获取超时 | 无(提交任务时,队列满则触发拒绝策略) | connectionTimeout |
连接池特有:定义获取一个连接的最大等待时间。防止线程在无法获取连接时被无限期阻塞。这是非常重要的保护机制。 |
泄漏检测 | 无此概念 | leakDetectionThreshold |
连接池特有:监控连接被借用后是否在规定时间内归还,用于发现代码中未正确关闭连接的错误。 |
配置线程池时你想的是"我的机器能同时干多少活 ";而配置数据库连接池时你想的是"我的数据库能同时承受多少访问,我如何确保每次访问用的连接都是好的"。