Java应用Oracle主备切换后无法自动恢复连接,根本原因是JDBC Thin驱动默认忽略TAF通知、不解析FAILOVER_MODE参数,且连接池未清理僵尸连接;须显式启用ALT、配置connection-test-query=SELECT 1 FROM DUAL及validation-timeout,并确保数据库侧alt_grace_period已设非零值。

Java 应用在 Oracle 19c 主备切换后无法自动重连,根本不是连接串写错了,而是 JDBC Thin 驱动压根没触发重连逻辑——它默认忽略 TAF,也不解析 FAILOVER_MODE,更不会监听服务端角色变化。
你看到的 ORA-03113、ORA-12516 或连接池里“能取到连接但一用就报错”,基本都卡在这三个环节:TNS 配置没生效、JDBC URL 没走对路径、连接池没做连接验证。
tnsnames.ora 里配了 FAILOVER_MODE,为什么还是不切换?
TAF 不是“写了就生效”的开关,它依赖客户端完整解析 TNS 别名,而 JDBC Thin 驱动只在 显式引用 tnsnames 别名 时才读取 FAILOVER_MODE。用 Easy Connect 格式(如 jdbc:oracle:thin:@//host:1521/orcl)会直接跳过整个 TNS 解析流程。
常见错误现象:
-
tnsping DG_SERVICE通,但 Java 连不上新主库 - 日志里反复出现
IO Error: Connection reset,没有新 TCP 连接建立
必须满足:
-
tnsnames.ora中定义完整FAILOVER_MODE块(不能只写FAILOVER=ON) -
ADDRESS_LIST至少含两个地址,且顺序为主库在前、备库在后 - JDBC URL 必须形如
jdbc:oracle:thin:@DG_SERVICE,而非 IP+端口直连 - 驱动版本 ≥ ojdbc8(12.2+),且
TnsAdmin环境变量或 JVM 参数已指向tnsnames.ora所在目录
JDBC URL 加了 failover=true 就够了吗?
不够。failover=true 只是 JDBC 层的一个“使能开关”,它本身不携带任何故障转移策略。真正起作用的是 TNS 层的 FAILOVER_MODE,而 JDBC 只负责把控制权交给它。
容易踩的坑:
- 在 URL 里加
loadBalance=true却没配 RAC 多节点地址,导致连接被随机打到已宕机实例 - 混用
failover=true和 FCF(Fast Connection Failover)参数,两者互斥,FCF 要求禁用所有 TNS 层重试逻辑 - 使用
oracle.jdbc.replay.ReplayDriver却没配TYPE=SELECT+WITH HOLD游标,结果 SELECT 语句中断后无法重放
正确做法:
- 若走 TAF,URL 保持简洁:
jdbc:oracle:thin:@DG_SERVICE - 不加
failover=true也能生效(驱动自动识别 TNS 配置),加了也无害,但别指望它替代 TNS 配置 - 若用 UCP 或 HikariCP,确保连接池未缓存僵尸连接——必须配
connection-test-query=SELECT 1 FROM DUAL和validation-timeout
切换后连上新主库,却报 ORA-12516:Listener refused the connection
这不是网络问题,是新主库根本没注册你要连的 SERVICE_NAME。Oracle 容灾切换后,新主库默认只注册 db_unique_name 对应的服务名(如 jiekexu),而你在 tnsnames.ora 里写的 SERVICE_NAME=orcl_dg 并不会自动注册。
典型表现:
-
lsnrctl status输出里看不到你的 service 名 -
tnsping DG_SERVICE成功,但 SQL*Plus 连接时报ORA-12516
必须在切换脚本末尾补两行:
ALTER SYSTEM SET service_names='orcl_dg' SCOPE=BOTH;ALTER SYSTEM REGISTER;
注意:service_names 值含逗号或空格时,要用单引号包裹;执行前确保实例处于 OPEN 状态。
为什么 HikariCP 取到连接后第一次执行就失败?
因为连接池里的连接是“懒验证”的——它复用了切换前建好的旧连接,而该连接底层 TCP 已断,但池子不知道。直到你调用 Connection.createStatement() 才暴露异常。
这不是数据库或 TNS 的问题,是连接池健康检查缺失。
必须配置:
-
connection-test-query=SELECT 1 FROM DUAL(Oracle 必须带FROM DUAL) -
validation-timeout=3000(单位毫秒,太短易误判,太长拖慢响应) -
idle-timeout和max-lifetime设得比数据库sqlnet.expire_time小,避免连接被服务端静默回收
Druid 用户需额外开启 testWhileIdle=true 并设 timeBetweenEvictionRunsMillis,否则空闲连接永远不探活。
TAF 和连接池健康检查是两层事:TAF 解决“连哪个地址”,连接池解决“连上的是否还活着”。漏掉后者,再全的 TAF 配置也白搭。


















