Druid 连接池配置项与连接生命周期全解析项目XXXDruid 版本1.2.22数据库Oracle 12c RAC (2 节点, SCAN: db.xdxt.gsnx.dc:1522)整理日期2026-07-17目录一、Druid 全部配置项及作用1.1 连接池大小1.2 连接获取1.3 故障恢复1.4 连接验证1.5 空闲回收1.6 KeepAlive 保活1.7 PreparedStatement 缓存1.8 Filter 过滤器1.9 监控与统计1.10 泄漏回收1.11 事务1.12 JDBC 属性二、配置项间的关联关系2.1 关联总览图2.2 核心关联链路详解三、连接池内部结构四、getConnection 决策流程五、三种场景连接池状态变化六、连接完整生命周期6.1 阶段一创建CreateConnectionThread6.2 阶段二使用业务线程借还循环6.3 阶段三销毁DestroyThread6.4 三线程协调关系七、活跃连接堆栈八、生产故障关键判断速查一、Druid 全部配置项及作用补充内容完整流程1.1 连接池大小配置项作用生产值initial-size启动时创建的初始连接数init()时一次性创建40min-idle最小空闲连接数CreateConnectionThread 补充到此值前提activepooling maxActive40max-active最大活跃连接数连接池容量上限决定connections[]数组大小120约束关系initial-size≤min-idle≤max-active⚠️ 易误解min-idle的补充逻辑有前提条件——只有activeCount poolingCount maxActive池未满时CreateConnectionThread 才会创建连接补充到minIdle。池满时直接empty.await()阻塞不会建连。1.2 连接获取配置项作用生产值max-waitgetConnection()最大等待毫秒数超时抛GetConnectionTimeoutException10000机制控制notEmpty.park(maxWait)的超时时间。与break-after-acquire-failure配合——breaktrue 时 signal 无人响应max-wait 到期必超时。1.3 故障恢复配置项作用生产值break-after-acquire-failure建连连续失败后是否终止建连线程trueconnection-error-retry-attempts终止前最大建连失败次数3time-between-connect-error-millis建连失败重试间隔10000⚠️ 三个必须一起看break-after-acquire-failuretrue时retry-attempts和time-between-error才有意义。链路建连失败 → 等 time-between-error(10s) → 重试 → 重复 retry-attempts(3) 次 → breaktrue → CreateConnectionThread 死亡 → CreatingCount 永久01.4 连接验证配置项作用生产值validation-query验证用的 SQLSELECT 1 FROM DUALvalidation-query-timeout验证 SQL 超时秒数1test-while-idle空闲连接定时验证DestroyThread 周期执行truetest-on-borrow借出连接时验证每次取连接都验证开销大falsetest-on-return归还连接时验证失败则丢弃不回池false依赖关系validation-query是所有 test 操作的前提test-while-idle依赖time-between-eviction-runs-millis。推荐组合test-while-idletruetest-on-borrowfalsetest-on-returnfalse空闲时验证借还不验证性能与安全兼顾1.5 空闲回收配置项作用生产值time-between-eviction-runs-millisDestroyThread 检查间隔所有回收逻辑的心跳周期60000min-evictable-idle-time-millis连接最小空闲时间超过才可回收300000max-evictable-idle-time-millis连接最大空闲时间超过强制回收必须 min-evictable900000phy-timeout-millis物理连接累计存活时间超时触发销毁与 idle 时间无关0不限不配time-between-eviction-runs-millis则 DestroyThread 不运行所有回收逻辑失效。1.6 KeepAlive 保活配置项作用生产值keep-alive是否对空闲连接做保活falsekeep-alive-between-time-millis保活检查间隔120000与 min-evictable 配合未超min-evictable但超keep-alive-between→ 执行validation-query保活不回收超min-evictable→ 回收销毁1.7 PreparedStatement 缓存配置项作用生产值pool-prepared-statements是否开启 PSCachetruemax-open-prepared-statements全局最大 PSCache 数0不限0max-pool-prepared-statement-per-connection-size每连接最大 PSCache 数超过时 LRU 淘汰20依赖关系pool-prepared-statementstrue是另外两个配置的前提。1.8 Filter 过滤器配置项作用生产值filters过滤器列表stat, wall, log4j, slf4jstat,wallfilter.stat.slow-sql-millis慢 SQL 阈值3000filter.stat.log-slow-sql是否记录慢 SQLtruefilter.stat.merge-sql是否合并相似 SQLtruefilter.wall.xxxSQL 防火墙配置启用filter.log4j.xxxSQL 日志配置—⚠️ wall filter 会拦截 PL/SQL 匿名块BEGIN...END需改用SELECT ... FROM DUAL或去掉 wall。1.9 监控与统计配置项作用生产值stat-view-servlet.enabled是否开启监控页truestat-view-servlet.login-username/password监控页认证admin/123456stat-view-servlet.allow/denyIP 白名单/黑名单—stat-view-servlet.reset-enable是否允许重置统计trueweb-stat-filter.enabled是否开启 Web 请求监控独立于 stat-view-servlettrueweb-stat-filter.url-patternWeb 监控 URL 模式/*web-stat-filter.session-stat-enable是否统计 Sessiontruetime-between-log-stats-millis统计日志输出间隔60000aop-patternsAOP 监控的包路径com.dhcc.cms.*依赖关系监控统计类配置依赖filters包含stat否则无数据可展示。1.10 泄漏回收配置项作用生产值remove-abandoned是否回收泄漏连接falseremove-abandoned-timeout连接被借出多久算泄漏秒300log-abandoned是否记录泄漏连接堆栈falseremove-abandonedtrue是另外两个的前提。log-abandonedtrue时Druid 监控页活跃连接堆栈才有数据。1.11 事务配置项作用生产值default-auto-commit默认自动提交truedefault-read-only默认只读falsedefault-transaction-isolation默认事务隔离级别-1使用驱动默认1.12 JDBC 属性配置项作用生产值driver-class-nameJDBC 驱动类oracle.jdbc.OracleDriverurl连接 URLjdbc:oracle:thin:db.xdxt.gsnx.dc:1522/xdxtusername/password认证凭据xdxt / ****connection-propertiesJDBC 连接属性oracle.jdbc.ReadTimeout60000 等Oracle 关键超时参数通过connection-properties或 URL 参数设置oracle.net.CONNECT_TIMEOUT建连 TCP 握手超时oracle.jdbc.ReadTimeoutSocket 读写超时影响所有 SQL 执行含 validation-query二、配置项间的关联关系2.1 关联总览图max-active (池容量上限) ├─ initial-size → 启动时创建 ├─ min-idle → CreateConnectionThread 最低水位 ├─ break-after-acquire-failure ─┬─ connection-error-retry-attempts │ └─ time-between-connect-error-millis └─ max-wait → 取连接等待超时 time-between-eviction-runs-millis (DestroyThread 心跳) ├─ test-while-idle ──┐ │ ├─ validation-query ── validation-query-timeout │ └─ (test-on-borrow / test-on-return 也依赖 validation-query) ├─ min-evictable-idle-time-millis ── max-evictable-idle-time-millis ├─ keep-alive ── keep-alive-between-time-millis ├─ remove-abandoned ─┬─ remove-abandoned-timeout │ └─ log-abandoned └─ phy-timeout-millis filters (过滤器) ├─ stat ─┬─ stat-view-servlet (监控页) │ ├─ time-between-log-stats-millis (统计日志) │ ├─ filter.stat.slow-sql-millis (慢SQL) │ └─ web-stat-filter (Web监控) ├─ wall → filter.wall.xxx (SQL防火墙) └─ log4j → filter.log4j.xxx (SQL日志) pool-prepared-statements (PSCache开关) ├─ max-open-prepared-statements (全局上限) └─ max-pool-prepared-statement-per-connection-size (单连接上限)2.2 核心关联链路详解链路 1连接池容量控制max-active (上限) ├─ ≥ initial-size (启动创建量) ├─ ≥ min-idle (最低水位) └─ max-wait → getConnection 超时策略max-active决定了connections[]数组的容量initial-size和min-idle不能超过它。max-wait控制池满或无法建连时的等待超时。链路 2故障恢复生产故障核心break-after-acquire-failure true ├─ connection-error-retry-attempts 3 (失败3次) └─ time-between-connect-error-millis 10000 (间隔10秒) ↓ 建连失败 → 等10秒 → 重试 → 重复3次 → breaktrue ↓ CreateConnectionThread 死亡 ↓ CreatingCount 永久 0 ↓ getConnection 发 empty.signal() 无人响应 ↓ max-wait(10秒) 到期 → GetConnectionTimeoutException这是生产故障的核心链路Oracle 卡住 → 建连失败 3 次 → 线程死亡 → 池子有空位但建不了连 → 超时。链路 3连接验证与回收time-between-eviction-runs-millis 60000 (每60秒检查一次) │ ├─ 对空闲连接(pooling) │ ├─ test-while-idletrue → 执行 validation-query 验证 │ │ ├─ 验证通过 → 保留 │ │ └─ 验证失败 → 丢弃(DiscardCount) │ ├─ 空闲时间 min-evictable(5分钟) → 可回收 │ └─ 空闲时间 max-evictable(15分钟) → 强制回收 │ └─ 对活跃连接(active) └─ remove-abandonedtrue 时 → 借出 remove-abandoned-timeout(5分钟) → 强制回收链路 4Filter 与监控filters stat,wall │ ├─ stat → 数据收集 │ ├─ stat-view-servlet → /druid/* 监控页 │ ├─ time-between-log-stats-millis → 统计日志输出 │ ├─ filter.stat.slow-sql-millis → 慢SQL标记 │ └─ web-stat-filter → Web请求维度统计 │ └─ wall → SQL防火墙 └─ 拦截不合规SQL如 BEGIN...END 匿名块三、连接池内部结构DruidDataSource 内部维护一个DruidConnectionHolder[] connections数组这就是池子。数组被两个游标分为三个区域connections[] 数组: ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │ P0 │ P1 │ P2 │ A3 │ A4 │ A5 │ 空位 │ 空位 │ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ ←── poolingCount3 ──→ ←── activeCount3 ──→ ←── 空位 ──→ ↑ maxActive8, 已用6个, 剩余2个空位前poolingCount个空闲连接Pooling等着被取走中间activeCount个已借出的连接Active正在被业务使用后面空位可以放入新建的连接约束poolingCount activeCount ≤ maxActive四、getConnection 决策流程getConnection(maxWait)的核心逻辑有三条路径getConnection(maxWait10000) │ ├─ 路径A: poolingCount 0? │ ├─ YES → 从 connections[poolingCount-1] 取走连接 │ │ → poolingCount--, activeCount │ │ → (remove-abandonedtrue 时记录活跃堆栈) │ │ → 返回连接 (0ms) ✅ │ │ │ └─ NO → 进入路径B/C判断 │ ├─ 路径B: poolingCount0, activeCount maxActive? │ ├─ YES → empty.signal() ← 唤醒 CreateConnectionThread 去建连 │ │ → notEmpty.park(maxWait) ← 当前线程等待 │ │ │ │ │ ├─ 建连成功 → notEmpty.signal() → 拿到新连接 ✅ │ │ └─ maxWait超时 → GetConnectionTimeoutException ❌ │ │ │ └─ NO → 进入路径C │ └─ 路径C: poolingCount0, activeCountmaxActive (池满) │ → notEmpty.park(maxWait) ← 纯等别人归还不建连 │ ├─ 有人归还 → notEmpty.signal() → 拿到归还的连接 ✅ │ └─ maxWait超时 → GetConnectionTimeoutException ❌路径B的子场景breaktrue生产故障路径B判断: poolingCount0, activeCount25 maxActive120 → empty.signal() ← 发出信号 → notEmpty.park(10000) ← 等待 │ ├─ CreatingCount1 (线程活着)? │ → CreateConnectionThread 收到信号尝试建连 │ └─ CreatingCount0 (线程已死, breaktrue)? → empty.signal() 发了但无人响应 ❌ → 10秒后 → GetConnectionTimeoutException → 堆栈中包含业务代码 (如 user_row_qry)关键路径B和breaktrue子场景的连接池状态完全相同pooling0, activemax从 Druid 监控页看到的ActiveCount和PoolingCount分不出区别。唯一的判断依据是CreatingCount。五、三种场景连接池状态变化场景A正常取连接poolingCount 0Before: After: ┌────┬────┬────┬────┬────┐ ┌────┬────┬────┬────┬────┐ │ P0 │ P1 │ A2 │ A3 │ 空 │ │ P0 │ A1 │ A2 │ A3 │ 空 │ └────┴────┴────┴────┴────┘ └────┴────┴────┴────┴────┘ pooling2 active2 pooling1 active3 maxActive5 maxActive5 → 从 connections[1] 取走 P1 → P1 变成 A1 → poolingCount 2→1, activeCount 2→3 → 0ms 返回 ✅场景B池空建新连pooling0, active max, 线程活着Before: After: ┌────┬────┬────┬────┬────┐ ┌────┬────┬────┬────┬────┐ │ A0 │ A1 │ A2 │ 空 │ 空 │ │ A0 │ A1 │ A2 │ A3 │ 空 │ └────┴────┴────┴────┴────┘ └────┴────┴────┴────┴────┘ pooling0 active3 pooling0 active4 maxActive5 maxActive5 → empty.signal() → CreateConnectionThread 建连 → 新连接填入 connections[3] → 变成 A3 → activeCount 3→4 ✅场景C生产故障pooling0, active max, 线程已死Before: After (10秒后): ┌────┬────┬────┬────┬────┐ ┌────┬────┬────┬────┬────┐ │ A0 │ A1 │ A2 │ 空 │ 空 │ │ A0 │ A1 │ A2 │ 空 │ 空 │ └────┴────┴────┴────┴────┘ └────┴────┴────┴────┴────┘ pooling0 active3 pooling0 active3 maxActive5 maxActive5 CreatingCount0 (线程已死!) → 10秒超时 ❌ → empty.signal() 发出但无人响应 → notEmpty.park(10000) 等待 → 10秒后 GetConnectionTimeoutException → 池状态无任何变化有2个空位但填不进去场景B和C的连接池状态完全相同唯一区别是CreatingCountB1活着C0已死。六、连接完整生命周期连接的生命周期由 Druid 内部三个线程协作管理6.1 阶段一创建CreateConnectionThread┌─────────────────────────────────────────────────────────┐ │ CreateConnectionThread │ │ │ │ 触发方式1: init() 启动时 │ │ → 循环创建 initial-size(40) 个连接 │ │ → 全部放入 connections[] → poolingCount 40 │ │ │ │ 触发方式2: 主动补充 │ │ → 循环检查: poolingactive min-idle(40) ? │ │ → YES 且 max-active → createPhysicalConnection() │ │ → 放入 connections[] → poolingCount │ │ → 继续检查直到 ≥ min-idle 或 ≥ max-active │ │ → empty.await() 等待下次信号 │ │ │ │ 触发方式3: 被动响应 │ │ → 收到 getConnection 的 empty.signal() │ │ → 如果 activepooling max-active │ │ → createPhysicalConnection() │ │ → 放入 connections[] → poolingCount │ │ → notEmpty.signal() → 唤醒等待的 getConnection │ │ │ │ ⚠️ 故障处理: │ │ → 建连失败 → 等 time-between-error(10s) │ │ → 重试 retry-attempts(3) 次 │ │ → 超过 → breaktrue → 线程死亡 │ │ → CreatingCount 永久0 │ │ │ │ 涉及配置: │ │ initial-size, min-idle, max-active, │ │ break-after-acquire-failure, │ │ connection-error-retry-attempts, │ │ time-between-connect-error-millis │ └─────────────────────────────────────────────────────────┘6.2 阶段二使用业务线程借还循环┌─────────────────────────────────────────────────────────┐ │ 业务线程借还循环 │ │ │ │ 借出 (getConnection): │ │ → poolingCount 0? │ │ │ YES → 取 connections[poolingCount-1] │ │ │ poolingCount--, activeCount │ │ │ (remove-abandonedtrue 时记录活跃堆栈) │ │ │ → 返回连接 │ │ │ │ │ │ NO → activeCount maxActive? │ │ │ YES → empty.signal() notEmpty.park(maxWait) │ │ │ → 拿到新连接 or 超时 │ │ │ NO → notEmpty.park(maxWait) 等归还 │ │ │ → 拿到归还连接 or 超时 │ │ │ │ │ 使用: │ │ → test-on-borrowtrue? 执行 validation-query 验证 │ │ → 执行业务 SQL │ │ → SQL 执行完毕 │ │ │ │ 归还 (recycle): │ │ → test-on-returntrue? 执行 validation-query 验证 │ │ │ 通过 → 放回 connections[] → poolingCount │ │ │ 失败 → 丢弃连接 → DestroyCount │ │ │ │ │ → test-on-returnfalse → 直接放回池中 │ │ → notEmpty.signal() → 唤醒等待的 getConnection │ │ │ │ connections[] 数组状态变化: │ │ 借出前: |P0|P1|P2|A3|A4| pooling3, active2 │ │ 借出后: |P0|P1|A2|A3|A4| pooling2, active3 │ │ 归还后: |P0|P1|P2|A3|A4| pooling3, active2 │ │ │ │ 涉及配置: │ │ max-wait, test-on-borrow, test-on-return, │ │ validation-query, validation-query-timeout, │ │ remove-abandoned, log-abandoned │ └─────────────────────────────────────────────────────────┘6.3 阶段三销毁DestroyThread┌─────────────────────────────────────────────────────────┐ │ DestroyThread │ │ │ │ 心跳: 每 time-between-eviction-runs-millis(60s) 唤醒 │ │ │ │ 对空闲连接(pooling)处理: │ │ 遍历 connections[0..poolingCount-1]: │ │ │ │ │ ├─ test-while-idletrue? │ │ │ → 执行 validation-query 验证 │ │ │ → 验证超时(validation-query-timeout)? │ │ │ YES → 丢弃 → DiscardCount │ │ │ → 验证失败(SQL异常)? │ │ │ YES → 丢弃 → DiscardCount │ │ │ │ │ ├─ 空闲时间 min-evictable(5分钟)? │ │ │ YES → 可回收 → physicalConnection.close() │ │ │ → DestroyCount │ │ │ │ │ ├─ 空闲时间 max-evictable(15分钟)? │ │ │ YES → 强制回收 → physicalConnection.close() │ │ │ → DestroyCount │ │ │ │ │ ├─ phy-timeout-millis 0 且 累计存活 phy-timeout? │ │ │ YES → 回收与空闲时间无关 │ │ │ │ │ └─ keep-alivetrue 且 超过 keep-alive-between? │ │ → 执行 validation-query 保活不回收 │ │ → KeepAliveCheckCount │ │ │ │ 对活跃连接(active)处理: │ │ 遍历 connections[poolingCount..poolingCountactive-1]: │ │ │ │ │ └─ remove-abandonedtrue? │ │ → 借出时间 remove-abandoned-timeout(300秒)? │ │ YES → 强制回收连接 │ │ → log-abandonedtrue 时输出借出时的堆栈 │ │ → RemoveAbandonedCount │ │ → 回收后 notEmpty.signal() │ │ │ │ 最终: physicalConnection.close() → 物理连接断开 │ │ │ │ 涉及配置: │ │ time-between-eviction-runs-millis, │ │ test-while-idle, validation-query, │ │ validation-query-timeout, │ │ min-evictable-idle-time-millis, │ │ max-evictable-idle-time-millis, │ │ phy-timeout-millis, keep-alive, │ │ keep-alive-between-time-millis, │ │ remove-abandoned, remove-abandoned-timeout, │ │ log-abandoned │ └─────────────────────────────────────────────────────────┘6.4 三线程协调关系connections[] 数组 ┌─────────────────┐ CreateThread ────→│ 添加连接() │ │ │ 业务线程 ←───→│ 借出/归还 │ │ │ DestroyThread ──→│ 移除连接(-) │ └─────────────────┘ 协调信号: empty Condition: CreateThread 等待/唤醒 notEmpty Condition: 业务线程的 getConnection 等待/唤醒 数据流: CreateThread → connections[] → 业务线程(借出) ↓ 业务线程(归还) → connections[] ↓ DestroyThread ← connections[] (定期扫描回收)三个线程各司其职CreateConnectionThread往池里加连接init创建 补充到minIdle 响应signal建连业务线程从池里借/还连接getConnection recycleDestroyThread从池里移除连接验证失败回收 空闲超时回收 泄漏回收 物理超时回收七、活跃连接堆栈监控的是什么每个被借出active的连接在借出那一刻的调用线程堆栈。当某个线程调用getConnection()拿走一个连接还没归还时Druid 记录这个线程当时的完整堆栈——哪个类、哪个方法、第几行代码借的连接。开启条件只有remove-abandoned: true时Druid 才会在每次getConnection()时记录堆栈。remove-abandoned: false时活跃堆栈页面为空。spring:datasource:druid:remove-abandoned:trueremove-abandoned-timeout:300log-abandoned:true记录时机时间线: T1: 线程A调用 getConnection() ├─ poolingCount0, 有空位 ├─ empty.signal() → CreateConnectionThread 开始建物理连接 ├─ 线程A notEmpty.park() 等待 │ T2: CreateConnectionThread 建连成功 ├─ 新连接放入 connections[] 数组 ├─ poolingCount ├─ notEmpty.signal() │ T3: 线程A 被唤醒 ├─ 从 connections[] 取走这个新连接 ├─ poolingCount--, activeCount ├─ ★ 这里记录活跃堆栈 ★ ← 是这一步 ├─ 返回连接给业务代码 └─ 线程A拿着连接去执行 SQL关键堆栈记录的是线程A在T3时刻拿到连接的调用栈“谁在调 getConnection”不是 CreateConnectionThread 的内部建连堆栈。用途用途说明连接泄漏定位连接借走后长时间不归还看堆栈就知道是哪段代码借的慢SQL占用诊断activeCount25 时看25个连接分别卡在哪段代码故障类型区分池耗尽(120个堆栈全是慢SQL) vs 建连线程终止(25个堆栈, pooling0, creatingCount0)访问方式# 监控页 http://host:port/druid/datasource.html → 点击数据源 → 活跃连接堆栈 # JSON 接口 http://host:port/druid/activeConnectionStackTrace.json性能开销每次getConnection()记录一次Thread.getStackTrace()开销在微秒级对性能影响可忽略。生产环境建议开启remove-abandoned: true。八、生产故障关键判断速查Druid 监控项判断表PoolingCountActiveCountCreatingCount判断 0任意0 或 1✅ 正常取连接 0ms0 maxActive1⚠️ 池空了正在建连0 maxActive如 25/1200❌CreateConnectionThread 已终止breaktrue 触发过0 maxActive0连接池耗尽非本次故障场景生产故障状态ActiveCount 25/120 (21%) ← 远未占满 PoolingCount 0 ← 没有空闲连接 CreatingCount 0 ← 建连线程已死 CreateErrorCount 0 ← 建连失败过 → 有 95 个空位但建连线程已死无法填充池子 → getConnection 10秒超时 → GetConnectionTimeoutException → 堆栈含 user_row_qry 业务代码常见误区误区正确理解“poolingCount0 说明连接池耗尽了”不一定activemax 且 creating0 才是线程终止activemax 才是池耗尽“停掉监听能复现生产故障”lsnrctl stop 只阻断新连接握手已有连接不受影响无法复现“min-idle 会一直补充到 minIdle 个”前提是 activepooling maxActive池满时不触发“建连超时就是超时报错”TCP拒绝是瞬间失败日志是create connection error不是timeout“活跃堆栈记录的是建连过程”记录的是业务线程拿到连接的调用栈不是建连线程的内部堆栈文档结束本文档基于甘肃农信 xdxt 系统生产故障分析整理涵盖 Druid 1.2.22 全部配置项、配置间关联关系、连接池内部结构、getConnection 决策流程、三种场景对比、连接完整生命周期、活跃连接堆栈机制等内容。