1 嵌套事物补偿方案

1.1 简介

事物A是事物B的外层事物,事物B提交成功,但事物A提交失败,对事物B提交成功的数据进行补偿。

1.2 几个概念

事物回滚

当一个事物被提交给DBMS(数据库管理系统,例如MqSql),则DBMS需要确保该事物中的所有操作全部成功且保存在数据库中,如果事物中有的操作没有成功,DBMS确保所有操作都需要被回滚,既要么全执行,要么全都不执行。

事物传播

常用的两种传播级别如下:

REQUIRED – 需要事务,没有事务开启事务,已有事务加入当前事务;

REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

分布式事物

如果涉及到多个事物的提交,不能通过单个DBMS来确保多个事物的全部执行,或全部不执行操作。

1.3 哪里使用了

首先抽象出支付系统里的两个表:

支付计划表:支付系统内记录外部业务订单的表;

支付账户表:记录 企业账户/个人账户 余额的表。

一个事物

一个DB内对支付计划表和支付账户表的操作,所以可以确保,全部成功或全部失败。

多个事物

为了确保对 支付账户表 操作的性能,支付系统把支付账户表操作的事物独立出来。使用了前面讲到的事物传播级别的 REQUIRES_NEW。这样的话 支付计划表 和 支付账户表 属于两个不同的事物,就会有嵌套事物问题。

1.4 解决实现

支付计划表的操作事物命名为 事物A,支付账户表的操作事物命名为事物B。事物B 由 事物A 传播而来。

支付账户表操作

BpAccountController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 账户余额操作
AccountOperResult addBalance();

/**
* 确认账户操作
* @param confirmCode 确认码
*/
boolean confirm(String confirmCode);

/**
* 取消账户操作
* @param confirmCode 确认码
*/
boolean cancel(String confirmCode);
账户操作确认表

pay_acc_confirm_record,支付账户操作的确认记录, 记录了补偿账户需要的一些数据。confirmCode(操作确认码),为这次账户确认操作的UUID。

操作过程
  1. 首先在事物A 里面对支付计划表(等) 进行业务操作;
  2. 在事物A 的基础上,新开一个事物B,在事物B 内调用 BpAccountController.addBalance(),对账户余额进行加减操作,并生成账户操作确认记录;
  3. 事物B 提交失败,回滚事物B,事物A 抛出异常 ,回滚事物A,整个支付失败结束;
  4. 事物B 提交成功,将账户操作确认码记录在线程本地变量内。并在事物A 里面执行 BpAccountController.confirm(String confirmCode)操作,这个操作将会在事物A 正常提交成功后,删除账户操作确认记录;
  5. 事物A 提交失败,事物A回滚,在事物A catch 的异常里面,找到线程本地变量的确认码,执行 BpAccountController.cancel(String confirmCode) 操作,对账户余额进行补偿等操作,整个支付失败;
  6. 事物A 提交成功,confirm操作提交成功,删除了账户操作确认记录。整个支付完成。