bigywater.tech
Spring · 37

多数据源事务:各自提交的风险与分布式事务的边界

同一个服务里写两个数据库,一个 @Transactional 管得住吗?嵌套两个事务模板呢?提交时一方失败会怎样?这篇在 Spring Framework 7.0.9 和 MySQL 26.7.0 上用杀连接制造提交失败,逐个量出来。

7 个里 4 个
多数据源的七个场景中两库行数不一致的个数
A 0 行,B 1 行
@Transactional(txA) 写 A 和 B 后抛异常的结果
A 1 行,B 0 行
B 外 A 内、A 先提交、B 提交前连接被杀的结果

事务管理器只管自己的数据源

实测 · 本机执行输出
  (0)@Transactional(txA) 写 A 和 B,成功                → 正常返回                                             A 库 1 行,B 库 1 行 → 一致
  (1)@Transactional(txA) 写 A 和 B,最后抛异常             → IllegalStateException ← IllegalStateException    A 库 0 行,B 库 1 行 → 不一致
  (2)嵌套模板:A 外 B 内,都成功                              → 正常返回                                             A 库 1 行,B 库 1 行 → 一致
  (3)嵌套模板:B 内层提交之后,A 外层抛异常                         → IllegalStateException ← IllegalStateException    A 库 0 行,B 库 1 行 → 不一致
  (4)嵌套模板:A 在提交前连接被杀(B 先提交)                        → TransactionSystemException ← SQLNonTransientConnectionException A 库 0 行,B 库 1 行 → 不一致
  (5)反过来:B 外 A 内,A 的连接在 A 提交前被杀                    → TransactionSystemException ← SQLNonTransientConnectionException A 库 0 行,B 库 0 行 → 一致
  (6)B 外 A 内,A 先提交,B 的连接在 B 提交前被杀                  → TransactionSystemException ← EOFException        A 库 1 行,B 库 0 行 → 不一致

一个方法同时写两个库,另一个库的写入不受 @Transactional 控制,不会回滚。

分步演示

多数据源事务的边界
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9 + 两个 DataSourceTransactionManager + HikariCP + 自己起的 MySQL 26.7.0(同一个实例里的 demo 与 demo2 两个库);提交失败是通过另一个连接对要提交的连接执行 KILL 制造的(第 4、5、6 个场景),不是真实的网络故障;场景 4 到 6 的异常链最底层的异常类型(SQLNonTransientConnectionException 或 EOFException)取决于被杀连接的状态,每次运行可能不同
    • 没有验证:XA / JTA 两阶段提交(见分布式事务系列);Spring Data 里已经移除的 ChainedTransactionManager;最大努力一次提交(best effort 1PC)之外的补偿方案(Saga、Outbox,见分布式事务系列);两个库在不同 MySQL 实例上的情形(这里是同一个实例里的两个库,故障模型一样但没有验证)
    京ICP备2026067502号-1京公网安备11010502063487号