跳到主要内容
版本:4.6.0

为什么事务下切换不了数据源

这是多数据源最高频的疑问,答案本身不复杂,但值得单独讲清楚。

核心机制​

两件事同时发生,导致切换失效:

1. 数据源切换是「请求时取连接」

@DS 只是把数据源名压进当前线程的 ThreadLocal 栈。真正决定用哪个库的时机,是每次获取 Connection 的时刻——框架从 ThreadLocal 栈顶读出 key,再去找对应的数据源。

2. Spring 事务会「提前锁定连接」

@Transactional 开启事务时会从数据源取一个 Connection,把它绑定到当前线程(TransactionSynchronizationManager)。在这个事务的整个生命周期内,任何 DataSourceUtils.getConnection() 都会拿到同一个连接,而不是重新去问路由。

两条规则叠加:

@Transactional 开启 → 连接已绑定到线程
↓
事务内 @DS("b") → ThreadLocal 被改了,但连接不会重新获取
↓
SQL 仍然打在原来那个库上

所以现象是「切换静默失效」:不报错,日志里也能看到 switch to the datasource named [b],但 SQL 实际打在 a 库上。

把顺序反过来就对了​

事务的开启时机取决于切面顺序。本框架的 @DS 切面默认 order = Ordered.HIGHEST_PRECEDENCE,先于事务切面执行,所以:

@DS("a")
@Transactional
public void doSomething() {
// 先切到 a,再开事务 → 事务绑定的就是 a 的连接
// 正确用法
}
@DS("a")
@Transactional
public void outer() {
inner(); // inner 上标了 @DS("b"),但这里拿到的还是 a 的连接
}

@DS("b")
public void inner() {
// 失效:仍在 a 库
}
危险

不要为了「先切库再开事务」去调低 spring.datasource.dynamic.aop.order。默认值就是刻意设定的,调低会让 @DS 在事务之后执行,拿到已被绑定的连接。

常见疑问​

用了 @Transactional 就完全不能切库了吗?​

事务外可以随便切。事务内不行。也就是说,同一个 Service 里「事务方法调事务方法」的嵌套场景,内层切换一定失效。

那单库事务还能用吗?​

完全可以。只要事务内不切换数据源,该用 @Transactional 就用——它比本地多数据源事务成熟得多,支持嵌套保存点、只读事务、隔离级别等完整语义。建议优先使用。

业务必须保证事务,又有多个库,怎么办?​

按场景三选一:

场景方案
单体应用、跨库@DSTransactional,见本地多数据源事务
微服务、跨服务Seata AT / XA,见Seata 事务
需要标准 JTA/XAAtomikos(驱动直接指向 XA 数据源)

选型对比见事务选型指南。

还有别的办法吗?​

如果数据源只有两三个、又不怕复杂,可以放弃本组件,把数据源手动组装成一个 JTA 事务管理器管理的 XA 数据源集合。这条路本文不展开,可自行搜索「多数据源 JTA」。

背景知识​

不熟悉分布式事务的话,建议先补齐概念:

  • 事务概念 —— ACID、JDBC 事务、Spring 事务
  • CAP 定理与 BASE 理论 —— 理解为什么「跨库强一致」代价高昂