@transactional(timeout=…)对oracle长查询无效,因其仅在提交/回滚阶段检查超时;真正生效的是jdbc层参数(如oracle.jdbc.readtimeout)、hikaricp连接池配置及sqlnet.ora中的sqlnet.recv_timeout等。

Oracle事务超时不是@Transactional(timeout=…)能控制的
Spring 的 @Transactional(timeout = N) 对 Oracle 数据库的长查询或锁等待几乎无效。它只在事务提交/回滚阶段检查耗时,而 Oracle 中一个被行锁阻塞的 UPDATE 可能卡住 30 秒才返回,但 Spring 直到整个方法执行完才判断是否超时——此时早已错过干预时机。
真正起作用的是 JDBC 层和 Oracle Net 的超时配置,它们决定“连接建立多久失败”“SQL 执行多久中断”“网络读写多久断开”。这些参数不写在 @Transactional 里,也不在 Spring 的事务管理器配置中。
-
spring.datasource.hikari.connection-timeout:控制从连接池获取连接的等待时间(默认30秒),适用于连接池耗尽场景 -
spring.datasource.hikari.validation-timeout:校验连接有效性时的超时(影响健康检查) - JDBC URL 中的
oracle.net.CONNECT_TIMEOUT和oracle.jdbc.ReadTimeout:前者管 TCP 建连,后者管 SQL 执行中的 socket 读超时(单位毫秒) - Oracle Wallet 场景下,
sqlnet.ora里的SQLNET.OUTBOUND_CONNECT_TIMEOUT和SQLNET.RECV_TIMEOUT会覆盖 JDBC 参数
Oracle Wallet 环境下超时参数优先级容易混乱
当使用 Oracle Wallet 时,JDBC 驱动会先加载 sqlnet.ora,再解析 tnsnames.ora,最后才应用 URL 中的参数。这意味着即使你在 application.yml 里写了 ReadTimeout=5000,只要 sqlnet.ora 里有 SQLNET.RECV_TIMEOUT=60(单位秒),实际生效的就是 60 秒。
验证方式很简单:启动时加 JVM 参数 -Doracle.net.traceLevel=16,看日志里最终使用的超时值来自哪一层。常见错误包括:
- Wallet 目录下
sqlnet.ora文件权限不对,JDBC 静默忽略该文件,退回到 JDBC 默认值 -
tnsnames.ora中服务名定义里嵌套了(RETRY_DELAY=1)这类重试配置,导致表象是“超时很长”,实则是反复重试 - JDK 17 默认启用 TLS 1.3,而某些 Oracle 19c 补丁版本对 TLS 1.3 的握手超时处理异常,表现为连接卡住 20–30 秒后才报
java.net.SocketTimeoutException: Read timed out
排查时先确认是连接超时还是查询超时
这两类超时的日志特征完全不同,混在一起查只会浪费时间:
- 连接超时(Connection Timeout):启动时报
Caused by: oracle.net.ns.NetException: The Network Adapter could not establish the connection,或接口首次调用时抛org.springframework.dao.DataAccessResourceFailureException,根本没走到 SQL 执行阶段 - 查询超时(Query Timeout):日志里能看到完整的 SQL(如
Executing prepared statement [UPDATE ...]),然后隔几秒才出现java.sql.SQLTimeoutException: ORA-01013: user requested cancel of current operation或Socket read timed out
如果是后者,说明事务已经开启、连接已建立,问题出在 Oracle 侧的锁竞争、统计信息陈旧或执行计划退化,此时调整 Spring 的 @Transactional 毫无意义,得看 AWR 报告或 v$session_longops。
多数据源场景下事务管理器与超时配置必须显式绑定
Spring Boot 多数据源时,@Transactional 默认走 primary 事务管理器,但 HikariCP 连接池和 JDBC 超时参数仍按数据源 bean 名隔离。容易踩的坑是:
- 给
secondaryDataSource配了ReadTimeout=3000,但忘了在对应 service 方法上写@Transactional(transactionManager = "secondaryTransactionManager"),结果事务由 primary 管理器接管,超时却由 secondary 连接池控制,行为不可预测 - Oracle Wallet 路径在不同数据源间复用,但
sqlnet.ora里WALLET_LOCATION是全局生效的,一个数据源改了 Wallet 路径,另一个可能意外失效 - Spring Boot 3.2 默认启用
spring.transaction.default-timeout全局设置,但它只影响@Transactional的 timeout 属性,对 JDBC 层超时完全无感
最稳妥的做法是:每个 @Configuration 类里声明的数据源 Bean,都配套声明同名的 HikariConfig 实例,并把所有超时参数(包括 Wallet 路径)硬编码进该实例,不依赖外部配置文件的隐式加载。











