根本原因是@Transactional(timeout=...)只在事务提交/回滚时检查总耗时,无法中断阻塞在JDBC socket读阶段的线程;真正生效的是JDBC URL的oracle.net.CONNECT_TIMEOUT和oracle.jdbc.ReadTimeout参数,以及sqlnet.ora中的SQLNET.RECV_TIMEOUT配置。

Oracle连接超时后不自动断开,根本原因不是连接池“没反应”,而是 JDBC 层和 Oracle Net 的超时配置未生效或被覆盖——@Transactional(timeout=...) 对连接卡死完全无效。
为什么 @Transactional(timeout=...) 在 Oracle 连接卡住时不起作用
Spring 的事务超时只在事务提交或回滚的那一刻检查耗时,而 Oracle 中一个被行锁阻塞的 UPDATE 可能卡在 JDBC socket 读阶段长达 30 秒,但 Spring 完全感知不到——它直到整个方法执行完才去算总时间。此时连接早已僵死,事务管理器连回滚都发不出。
-
@Transactional(timeout=5)不会中断正在等待数据库响应的线程 - 它不控制 TCP 建连、SQL 执行、网络读写任何一环
- 真正决定“连接是否该断”的是 JDBC URL 参数和
sqlnet.ora配置
必须配置的 JDBC 层超时参数(Oracle 特定)
这些参数直接作用于 Oracle JDBC 驱动,决定 socket 级别的行为:
-
oracle.net.CONNECT_TIMEOUT:TCP 连接建立超时(单位毫秒),建议设为3000 -
oracle.jdbc.ReadTimeout:SQL 执行过程中 socket 读取响应的超时(单位毫秒),这是防长查询/锁等待的关键,建议设为10000 - JDBC URL 示例:
jdbc:oracle:thin:@//host:1521/ORCL?oracle.net.CONNECT_TIMEOUT=3000&oracle.jdbc.ReadTimeout=10000 - 注意:URL 中的
&必须正确转义(YAML 中用单引号包裹整个 URL)
Oracle Wallet 环境下 sqlnet.ora 的优先级陷阱
当使用 Oracle Wallet 时,JDBC 驱动会先加载 sqlnet.ora,再解析 tnsnames.ora,最后才应用 URL 中的参数。这意味着:
-
sqlnet.ora中的SQLNET.RECV_TIMEOUT=60(单位秒)会覆盖 URL 里的oracle.jdbc.ReadTimeout=10000 - 若
sqlnet.ora文件权限不对(如非 600),JDBC 会静默忽略它,退回到驱动默认值(常为 0,即无限等待) - 验证实际生效值:启动时加 JVM 参数
-Doracle.net.traceLevel=16,查日志中ReadTimeout的最终取值来源
HikariCP 与连接池层的协同配置
HikariCP 自身不处理 SQL 执行超时,但它能防止“获取连接”这一步无限等待:
-
spring.datasource.hikari.connection-timeout:从连接池拿连接的最大等待时间(默认 30000 毫秒),适用于连接池耗尽场景 -
spring.datasource.hikari.max-lifetime:连接最大存活时间(建议设为1800000,即 30 分钟),强制刷新老连接,避免被 Oraclesqlnet.expire_time或防火墙踢掉 - 不要设
keepalive-time(HikariCP 1.0+ 已移除),改用connection-test-query+validation-timeout做有效性校验 - 校验语句必须兼容 Oracle:
SELECT 1 FROM DUAL
最易被忽略的是:Oracle 超时是分层叠加的——sqlnet.ora > JDBC URL > Spring 注解。调错一层,其他层全白配。上线前务必用 -Doracle.net.traceLevel=16 看一眼日志里最终生效的是哪个值。


















