根本原因是tnsnames.ora硬编码单实例地址且未启用FAILOVER_MODE,JDBC驱动不支持TAF、连接池未验证连接有效性,且新主库未注册service_names;须配置ADDRESS_LIST+FAILOVER_MODE、启用ALT、设置validationQuery并执行ALTER SYSTEM REGISTER。

主备切换后 JDBC 连接不自动重连?检查 tnsnames.ora 是否用了静态地址
Oracle 11g 主备切换后业务连接无法自动漂移,最常见原因是客户端连接串或 tnsnames.ora 中硬编码了单个实例的 IP 和端口。这种写法会让 JDBC 驱动始终尝试连老主库(哪怕它已宕机),不会主动探测新主库。
正确做法是使用 Oracle 的 ADDRESS_LIST + FAILOVER=ON,让客户端具备基础故障转移能力:
MYDB =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = db-primary)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = db-standby)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = mydb_svc)
(FAILOVER_MODE =
(TYPE = SELECT)
(METHOD = BASIC)
(RETRIES = 3)
(DELAY = 5)
)
)
)
-
ADDRESS_LIST必须包含主、备两个地址,顺序不重要;驱动会按列表顺序尝试,失败后自动切下一个 -
TYPE = SELECT表示支持只读语句自动重试(对查询类业务关键),TYPE = SESSION才支持事务级漂移(但 11g 下效果有限,不推荐依赖) - 注意:11g 默认不启用
FAILOVER,必须显式配置FAILOVER_MODE,否则形同虚设
JDBC URL 里没加 loadBalance 或 failover 参数?那是默认单点连接
即使 tnsnames.ora 配对了,如果 JDBC URL 直接写的是 jdbc:oracle:thin:@//host:port/service 这种短格式,就完全绕过了本地命名解析,也绕过了所有 failover 配置。
必须用完整 TNS 格式,并显式启用客户端故障转移:
jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=db-primary)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=db-standby)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=mydb_svc)(FAILOVER_MODE=(TYPE=SELECT)(METHOD=BASIC)(RETRIES=3)(DELAY=5))))
- 避免使用
@//host:port/service简写 —— 它不支持任何高可用语义 - 不要依赖
loadBalance=true:这是 RAC 场景用的,Data Guard 主备不是 RAC,开启反而导致连接随机打到备库(只读)而报错 - 11g 的 JDBC 驱动(如 ojdbc6.jar)对
FAILOVER_MODE支持稳定,但要求驱动版本 ≥ 11.2.0.3;低于此版本可能忽略RETRIES
应用层没处理 SQLException 中的 ORA-1033 / ORA-12514?连接池会卡死在无效连接上
即使配置了 failover,JDBC 驱动只负责建立新连接时的重试,不负责已有连接失效后的自动刷新。主备切换后,连接池里大量旧连接仍指向原主库(现在可能是只读状态),执行 SQL 时抛出 ORA-1033: ORACLE initialization or shutdown in progress 或 ORA-12514: TNS:listener does not currently know of service requested,但连接池默认不会主动剔除它们。
- 必须配置连接池的 validationQuery,例如
SELECT 1 FROM DUAL,并启用testOnBorrow=true或testWhileIdle=true - HikariCP 推荐用
connection-test-query=SELECT 1 FROM DUAL+connection-init-sql=ALTER SESSION SET CURRENT_SCHEMA=your_schema(可选) - Druid 要设
validationQuery: SELECT 1 FROM DUAL、testOnBorrow: true、removeAbandonedOnBorrow: true - 特别注意:11g 的
SELECT 1 FROM DUAL在只读备库上能执行,但SELECT SYSDATE FROM DUAL可能被拒绝 —— 验证语句务必轻量且兼容只读
监听器没开动态注册 or service_names 没同步?备库切为主后服务名不可达
主备切换后,新主库若未正确注册服务名到监听器,客户端即使连上了,也会收到 ORA-12514: TNS:listener does not currently know of service requested。这不是网络问题,而是监听器根本不知道这个 SERVICE_NAME。
- 确认新主库的
service_names参数和原主库一致:SHOW PARAMETER service_names;不一致就用ALTER SYSTEM SET service_names='mydb_svc' SCOPE=BOTH修正 - 确保监听器启用动态注册:检查
local_listener是否指向有效地址,且REMOTE_LISTENER不应指向已下线节点 - 手动触发注册:
ALTER SYSTEM REGISTER;(立即生效,无需重启) - 验证方式:
lsnrctl status输出中必须看到目标SERVICE_NAME处于READY状态,且HANDLER(S)数量 > 0
真实环境中,最常被忽略的是连接池验证逻辑与监听器服务名注册这两环——配置全对,但切完没手动 ALTER SYSTEM REGISTER,或连接池没配 testOnBorrow,结果业务持续报错十几分钟才恢复。











