在Java中,主库异常时不应在catch块中动态切备库,因事务已中断、连接不可靠、一致性难保障且违反分层职责;应通过AbstractRoutingDataSource前置路由,由重试机制自动切换读请求,写操作必须失败告警而非切库。
在 Java 应用中,当主数据库异常时,在 catch 块中“动态切换到备库”不是推荐做法,原因在于:事务已中断、连接状态不可靠、数据一致性难保障、且违反分层职责(异常处理不应承担路由逻辑)。真正可行的方式是将主备切换前置到数据访问层,由统一的数据库路由机制完成,catch 块只负责兜底或告警。
Spring 提供的 AbstractRoutingDataSource 是标准解法。它不直接持有连接,而是根据上下文(如线程变量)动态决定使用哪个目标数据源。
DataSource Bean(如 HikariCP 实例)AbstractRoutingDataSource,重写 determineCurrentLookupKey(),返回当前应选的 key(如 "master" 或 "slave")TransactionSynchronizationManager.isCurrentTransactionReadOnly() 或自定义 ThreadLocal<String> 控制路由策略DataSourceContextHolder.set("slave")
不要在 DAO 的 catch 块里写 set("slave") 并重试——这会破坏事务边界,且无法保证备库有最新数据。正确方式是:
@Retryable + 自定义 RetryPolicy)"master",第二次用 "backup")主备架构中,“写切换到备库”本质是故障转移(failover),需 DBA 人工介入或依赖高可用组件(如 MHA、Orchestrator、MySQL Group Replication)。Java 层不能擅自把写请求发给备库,否则会导致:
立即学习“Java免费学习笔记(深入)”;
SQLException(如 MySQL 的 ERROR 1290 (HY000))因此,写操作捕获异常后应直接返回错误码(如 503 Service Unavailable),而非尝试切库重试。
若业务允许短暂最终一致,可在主库不可用时启用降级策略: