Druid连接池:8个最常配错的参数、默认值与生产推荐值
【免费下载链接】druid阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池项目地址: https://gitcode.com/gh_mirrors/druid/druid
凌晨 3 点,应用集体抛出 GetConnectionTimeoutException,重启才恢复——这类故障十有八九是 Druid 连接池参数没配到位:连接被占满、死连接混进业务、泄漏无人发现。Druid 是阿里云 DataWorks 团队出品的数据库连接池,它的参数默认值大多偏向"安全"而非"高性能",照默认值裸奔能跑,但撑不过高峰。这篇文章按故障场景把最该动的参数一次讲清,并给出可直接拷贝的配置。
本文你能带走四样东西:
- 拿到一份带默认值、生产推荐值、改错后果的参数速查表
- 按"连接耗尽 / 死连接 / 泄漏"三大故障理解参数,出问题时知道动哪个
- 拷走 3 份按场景写好的完整配置(首次上线、核心交易、多副本)
- 学会用监控页和日志读数验证"改对了没有"
上节监控页就是本文后面所有"验证手段"的入口:配置改完,先看这里。
参数速查总表:默认值与生产推荐值
所有默认值取自 DruidAbstractDataSource.java,推荐值按常见单库、20 线程级并发的场景给出,具体数字后面每节都会解释。
| 参数 | 默认值 | 生产推荐值 | 改错的后果 |
|---|---|---|---|
| maxActive | 8 | 20~50 | 并发一上来就排队,数据库 max_connections 才是真上限 |
| maxWait | -1 | 3000~5000ms | -1 是无限等待,连接池一耗尽全部业务线程卡死 |
| minIdle | 0 | maxActive 的 1/4~1/2 | 低谷期没有热连接,流量突增时现场建连接,RT 毛刺 |
| testWhileIdle | true | true | 关掉后死连接要等下次检测才暴露 |
| timeBetweenEvictionRunsMillis | 60000 | 60000 | 调太小浪费,调太大死连接窗口变长 |
| minEvictableIdleTimeMillis | 1800000 | ≥ 数据库 wait_timeout | 小于它,空闲连接刚建好就被清掉 |
| phyTimeoutMillis | -1 | 3600000(1h) | -1 永不主动销毁,死连接只能被动发现 |
| removeAbandoned | false | 生产 false(定位期短暂开 true) | 强开会误杀长事务,源码注释明确不建议生产使用 |
连接为什么会被耗尽:maxActive 与 maxWait
应用报 GetConnectionTimeoutException,本质是"要连接的人"多于"池里的连接"。三个参数决定这个平衡:maxActive 定并发上限,maxWait 定等不到时线程的行为,minIdle 定冷启动表现。异常本身定义在 GetConnectionTimeoutException.java。
| 参数 | 默认值 | 生产推荐值 | 一句话原因 |
|---|---|---|---|
| initialSize | 0 | 5 | 启动时预热几个连接,避开第一波流量的建连开销 |
| maxActive | 8 | 20~50 | 默认 8 太小;超过 50 时先怀疑慢 SQL 或泄漏,别无脑加 |
| minIdle | 0 | maxActive 的 1/4~1/2 | 保持热连接,低谷不浪费、高峰不建连 |
| maxWait | -1 | 3000~5000ms | 设成保险丝:池耗尽时快速失败,而不是线程无限排队 |
| maxWaitThreadCount | -1 | 30~100 | 限制排队线程数,防止数据库故障时线程堆积打满 JVM |
# ===== 连接容量 ===== spring.datasource.druid.initial-size=5 # 启动预热连接数 spring.datasource.druid.max-active=20 # 并发上限,按业务线程数估算 spring.datasource.druid.min-idle=8 # 低谷期保留的热连接 spring.datasource.druid.max-wait=5000 # 拿不到连接 5 秒即抛异常,快速失败 spring.datasource.druid.max-wait-thread-count=50 # 排队线程封顶,防线程堆积注意:minIdle 不能大于 maxActive,否则 setMinIdle 会直接抛 IllegalArgumentException。
这节解决的是:连接池在高峰时"接不住"或"排队卡死"。
死连接怎么混进业务:testWhileIdle 组合
数据库重启、网络切换、超过 MySQL 的 wait_timeout,池里的物理连接就死了。验证机制没配对时,这些死连接会被借给业务线程,抛出五花八门的"通讯链路断裂"异常。
| 参数 | 默认值 | 生产推荐值 | 一句话原因 |
|---|---|---|---|
| validationQuery | null | SELECT 1 | 各库通用的最小验证 SQL(Oracle 用 SELECT 1 FROM DUAL) |
| testOnBorrow | false | false | 每次借出都验证一次,高并发下 RT 明显上升 |
| testOnReturn | false | false | 归还时验证同样浪费,靠空闲检测兜底即可 |
| testWhileIdle | true | true | 默认就是 true,别关:空闲检测是唯一常态化剔除死连接的手段 |
| timeBetweenEvictionRunsMillis | 60000 | 60000 | 60 秒一轮,兼顾开销与死连接暴露窗口 |
# ===== 连接有效性 ===== spring.datasource.druid.validation-query=SELECT 1 # 验证 SQL spring.datasource.druid.test-while-idle=true # 空闲时检测,剔除死连接 spring.datasource.druid.time-between-eviction-runs-millis=60000 # 检测间隔 60s spring.datasource.druid.min-evictable-idle-time-millis=3600000 # 空闲 1h 才允许清注意:minEvictableIdleTimeMillis 必须大于数据库侧的 wait_timeout(MySQL 默认 8 小时),否则连接被数据库断掉前池子自己就清了,清得越多重建越频繁。
这节解决的是:偶发"连接已关闭/通讯链路失效"类异常。
连接泄漏怎么被定位:removeAbandoned 的正确用法
泄漏的典型读数是 activeCount 只增不减,minIdle 兜不住、maxWait 被反复触发。Druid 的 removeAbandoned 会在连接借出超时后强制回收并打堆栈,但它在源码里的警告很直接:
# ===== 泄漏定位 ===== spring.datasource.druid.log-abandoned=true # 回收时打印借出堆栈,定位泄漏代码 spring.datasource.druid.remove-abandoned=false # 生产保持 false| 参数 | 默认值 | 生产推荐值 | 一句话原因 |
|---|---|---|---|
| logAbandoned | false | true | 只记堆栈不误杀,是定位泄漏的关键开关 |
| removeAbandoned | false | 生产 false,排障期短暂 true | 强回收会杀掉运行超过阈值的正常长事务 |
| removeAbandonedTimeoutMillis | 300000 | 按业务最长事务上浮 50% | 阈值低于最长事务时长就是误杀 |
注意:DruidDataSource 中 setRemoveAbandoned(true) 会打 warn 日志"removeAbandoned is true, not use in production"——把它当排障开关用,不当常态防护用。
定位手法:短暂开 removeAbandoned + logAbandoned,看日志里的借出堆栈找到漏关连接的代码;或者在监控页"Web应用"页对照 URI 的 JDBC 执行次数与活跃连接。修完代码后记得关回去。
这节解决的是:连接池被慢慢"吃光"却查不到源头。
物理连接该活多久:phyTimeoutMillis
phyTimeoutMillis 默认 -1,物理连接永不主动销毁;phyMaxUseCount 默认 -1,一个连接执行 SQL 没有次数上限。两者都意味着死连接和脏状态只能被动发现。
# ===== 物理连接生命周期 ===== spring.datasource.druid.phy-timeout-millis=3600000 # 1 小时主动换血 spring.datasource.druid.phy-max-use-count=100000 # 执行 10 万次后强制重建| 参数 | 默认值 | 生产推荐值 | 一句话原因 |
|---|---|---|---|
| phyTimeoutMillis | -1 | 3600000(1h) | 主动换血比等死连接被检测出来成本低 |
| phyMaxUseCount | -1 | 100000 | 给连接"服役时长"上限,防状态积累 |
这节解决的是:运行数天后才出现的偶发连接失效。
PreparedStatement 缓存到底开不开
池化让固定模板 SQL 复用服务端编译结果。CRUD 模板 SQL 多、重复执行的高并发场景收益明显;动态拼接 SQL 多的场景收益低还占内存。实现见 PreparedStatementPool.java。
| 参数 | 默认值 | 生产推荐值 | 一句话原因 |
|---|---|---|---|
| poolPreparedStatements | false | 模板 SQL 多则 true | 开启前先看 SQL 监控页确认 SQL 集中度 |
| maxPoolPreparedStatementPerConnectionSize | 10 | 50 | 每连接缓存条数,按业务 SQL 模板数定 |
| sharePreparedStatements | false | 保持 false | 跨连接共享引入额外复杂度,收益有限 |
# ===== PS 缓存(确认 SQL 模板集中后再开)===== spring.datasource.druid.pool-prepared-statements=true spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50注意:先开 stat 过滤看 SQL 模板集中度再决定开不开,别凭感觉开——模板 SQL 不集中时纯占内存。
这节解决的是:固定 SQL 高并发下 prepare 开销占比高的问题。
场景配方:三份可直接拷贝的配置
配方一:首次上线最小可用版
spring.datasource.druid.initial-size=5 spring.datasource.druid.max-active=20 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-wait=3000 # 快速失败,上线初期宁可报错也别卡线程 spring.datasource.druid.validation-query=SELECT 1 spring.datasource.druid.test-while-idle=true spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.log-abandoned=true # 上线初期多留一双眼睛为什么这么配:容量保守、失败快速、只开无副作用的防护。maxWait 设 3000ms 是为了让泄漏和慢 SQL 在第一周就暴露成异常,而不是被无限等待掩盖。
配方二:核心交易接口加固版
在配方一基础上追加:
spring.datasource.druid.max-wait=5000 spring.datasource.druid.max-wait-thread-count=50 spring.datasource.druid.phy-timeout-millis=3600000 spring.datasource.druid.min-evictable-idle-time-millis=3600000 spring.datasource.druid.connection-properties=druid.stat.slowSqlMillis=500 # 记录 >500ms 的 SQL为什么这么配:交易接口对毛刺敏感,物理连接 1 小时换血避免"服役过久"的连接;slowSqlMillis 设 500ms 让占用连接过长的 SQL 可追溯。
配方三:多副本生产版(8 副本 × 20 连接)
spring.datasource.druid.max-active=20 # 8 副本 × 20 = 160,需 < 数据库 max_connections spring.datasource.druid.async-init=true # 异步建连,缩短发布窗口 spring.datasource.druid.break-after-acquire-failure=true # 建连失败不再反复重试 spring.datasource.druid.connection-error-retry-attempts=1 # 重试 1 次即快速失败 spring.datasource.druid.min-idle=8为什么这么配:多副本下真正的上限是数据库侧连接数,先算总量再定单副本容量;asyncInit 让滚动发布少等几十秒;breakAfterAcquireFailure 防止数据库故障时每个副本都在后台反复建连,把故障放大成雪崩。
自诊断表:现象 → 原因 → 该动什么
| 现象 | 可能原因 | 该动的参数 / 动作 |
|---|---|---|
| GetConnectionTimeoutException 频发 | 连接耗尽或 maxWait 太短 | 先查 activeCount 是否贴 maxActive;是则优化慢 SQL,再考虑加 maxActive |
| 高峰 RT 毛刺、偶发建连延迟 | minIdle 为 0,现场建连接 | minIdle 提到 maxActive 的 1/4 |
| 偶发"连接已关闭/通讯链路断裂" | 死连接借出 | 确认 testWhileIdle=true;调 minEvictableIdleTimeMillis > wait_timeout |
| activeCount 缓慢增长不回落 | 连接泄漏 | 开 logAbandoned + 短暂开 removeAbandoned 抓堆栈 |
| 数据库故障后恢复慢、日志刷建连错误 | 默认不断重试建连 | breakAfterAcquireFailure=true,调小 connectionErrorRetryAttempts |
| 内存随运行时间上涨 | PS 缓存过开 | 调小 maxPoolPreparedStatementPerConnectionSize 或关闭池化 |
| 应用启动慢 | initialSize 大 + 同步初始化 | 开 asyncInit=true |
验证:怎么判断改对了
改完配置,用三个手段读数(对应监控页入口见文首截图):
- 监控页"数据源"页:看 ActiveCount 和 WaitThreadCount。正常读数:ActiveCount 明显低于 maxActive 且平稳,WaitThreadCount 长期为 0、只在流量尖刺时短暂出现个位数。
- 日志:观察
create connection、discard connection、remove abandoned connection关键词。正常读数:稳态下几乎没有 discard(频繁 discard 说明验证配置和 wait_timeout 打架);排障期能看到 remove abandoned 后跟借出堆栈。 - JSON API:监控页右上角"查看 JSON API",拉取
activeCount、recycleCount、errorCount与改配前对比。正常读数:errorCount 不随时间上涨,recycleCount 缓慢增长。
Druid 连接池的调优核心就三件事:把连接供给(容量与等待)算清楚,把死连接清理(检测与换血)配成闭环,把泄漏定位(堆栈日志)留在手边。每个参数都有默认值可查、推荐值可抄、监控读数可验证。下一步值得深入的是 SQL 监控与慢查询分析——找到真正占住连接过长的 SQL,比调任何参数都有效。你有哪类故障场景还没覆盖到?留言说说,下一期挑一个最常见的接着拆。
【免费下载链接】druid阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池项目地址: https://gitcode.com/gh_mirrors/druid/druid
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考