【无标题】

Druid 连接池与@Transactional 事务超时配置问题总结


一、@Transactional timeout 与 Druid 的关系

两者完全独立,互不感知

维度@Transactional timeoutDruid removeAbandonedTimeoutMillis
归属层Spring 事务管理层连接池层
作用对象事务的执行时长连接从池中借出后的存活时长
超时后行为标记事务为 rollback-only,下次 JDBC 操作时抛 TransactionTimedOutException直接关闭底层 socket,强制回收连接
相互通信❌ 没有任何通信机制❌ Druid 不知道 Spring 事务的存在

关键结论

  • @Transactional(timeout = 45) 设置 45s,不代表连接可以持有 45s
  • 如果 removeAbandonedTimeoutMillis = 10s,连接在借出 10s 后就会被 Druid 强制关闭,与 Spring 的 45s 毫无关联
  • 两个超时时间并不取较小值生效,而是各自独立触发,谁先到谁先动作

正确的设置原则

removeAbandonedTimeoutMillis > 业务中单次连接持有的最长时间 > @Transactional timeout

二、Druid 相关参数详解

2.1 参数汇总

参数默认值业内建议值说明
timeBetweenEvictionRunsMillis60000ms(1分钟)60000ms后台检测线程运行间隔,同时也是 keepAlive 心跳间隔,不必太频繁
minEvictableIdleTimeMillis1800000ms(30分钟)300000ms(5分钟)连接空闲超过此时间才有资格被回收,建议远小于 MySQL wait_timeout(默认28800s)
maxEvictableIdleTimeMillis25200000ms(7小时)900000ms(15分钟)空闲连接强制回收时间上限,建议设 15 分钟,防止 MySQL 主动断开后池里还握着死连接
removeAbandonedfalse生产 false,开发 true是否开启废弃连接回收,默认关闭
removeAbandonedTimeoutMillis300000ms(5分钟)开发 120s,生产如需开启则 ≥ 1800000ms连接借出后超过此时间未归还则认为废弃,核心约束:必须 > 业务最长事务耗时
logAbandonedfalsetrue发现废弃连接时打印调用栈日志,不关闭连接,排查泄漏用

2.2 removeAbandoned 生产环境是否常开?

不建议在生产环境常开 removeAbandoned=true

原因:

  • 连接持有时长计算从"借出连接"开始,SQL 执行期间同样计时,任何耗时较长的正常操作都可能被误判为泄漏连接而强制关闭
  • 开启后每次借还连接都需要操作一个带锁的 activeConnections Map,高并发下有额外性能开销
  • 连接泄漏属于代码 bug,应在开发测试阶段发现修复,不应靠生产运行时兜底

推荐策略:

环境removeAbandonedremoveAbandonedTimeoutMillislogAbandoned
开发/测试true120000ms(2分钟)true
生产false1800000ms(保留配置但不生效)true

2.3 生产环境推荐配置

spring:
  datasource:
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 3000                              # 获取连接等待超时 3s,快速失败

      time-between-eviction-runs-millis: 60000    # 检测线程间隔 60s
      min-evictable-idle-time-millis: 300000      # 空闲 5 分钟可回收
      max-evictable-idle-time-millis: 900000      # 空闲 15 分钟强制回收

      test-while-idle: true                       # 借出前检测连接有效性(推荐开)
      test-on-borrow: false                       # 每次借出都检测,性能损耗大,不开
      test-on-return: false
      validation-query: SELECT 1
      validation-query-timeout: 1

      remove-abandoned: false                     # 生产不开,防误杀
      remove-abandoned-timeout-millis: 1800000    # 即便不开,也配成合理值备用
      log-abandoned: true                         # 记录泄漏日志,帮助排查

三、案例复盘

3.1 案例描述

方法配置:@Transactional(rollbackFor = Exception.class, timeout = 45)
Druid 配置:removeAbandoned = true,removeAbandonedTimeoutMillis = 10000ms(10s)

执行过程:
  T=0s    借出连接,事务开始
  T=10s   ← Druid 后台线程扫描,连接借出已达 10s,判定为废弃连接,强制关闭客户端 socket
  T=0~20s 慢查询(花了 20s)—— MySQL 服务端连接仍在执行,客户端 socket 已断
  T=20~25s 两个 RPC 调用(5s)—— 不涉及 DB,没有立即报错
  T=25s   尝试提交事务 → 报错:Connection is closed ❌

3.2 为什么慢查询能"执行完"却最终报错?

Druid 关闭的是Java 客户端侧的 socket,MySQL 服务端不感知,SQL 在 DB 侧继续跑完。但 Java 侧持有的 Connection 对象已被标记为废弃关闭,等到后续执行 commit 时才真正抛出异常。这就是为什么报错出现在事务提交阶段,而不是慢查询执行阶段。

3.3 根本原因

removeAbandonedTimeoutMillis = 10s 远小于该方法的实际连接持有时间(25s),导致正常使用中的连接被误判为泄漏连接而强制回收。

3.4 解决方案(优先级从高到低)

  1. 优化慢查询(治本):20s 的查询需要排查索引、分页策略,目标控制在 1s 以内
  2. 事务内不做 RPC 调用:RPC 耗时不可控,占用数据库连接是反模式
  3. 生产关闭 removeAbandoned:把 remove-abandoned 改为 false,消除误杀风险
  4. 如必须开启removeAbandonedTimeoutMillis 至少要设置为业务最长事务耗时的 2 倍以上

3.5 核心记忆点

removeAbandonedTimeoutMillis 计时从连接借出那一刻开始,不管连接是否在执行 SQL,都在计时。SQL running 状态不会暂停计时器。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值