大事务拆分项目应用-再看事务嵌套

一、何为事务嵌套

最简单的事务嵌套情况:开启了一个事务的情况下,再开启一个事务2。

或者:在t1、t2分别为两个事务操作,而外层t3事务包含t1(t2),t3跟t1(t2)

         OR          

例如在支付系统中,在进行支付校验时,事务1负责更新账单状态&插入历史表,事务2负责创建支付记录及明细,如果假设更新账单状态成功,二创建支付记录失败,则导致账单状态与支付状态丢失数据一致性。故除保证事务1、2的原子性外,还得在1、2上层增加事务3以保证两个事务组合也具有原子性。

二、事务嵌套有何问题?

思考一个问题:理论上,t1事务执行完毕后,如果t2执行失败,往上t3因为t2执行失败而回滚。此时t1能回滚成功么?talk is cheap ,show me the case!测试case如下。

case结果:

1、session1中首先执行开始事务命令, start transaction;然后插入1条记录,查询session1中正常显示
2、在session2中查询该条记录,无匹配结果。 因为此时session1中未执行commit命令。mysql与oracle一样,commit的数据除了当前session之外,其他session是看不到的。
3、直接开启第二个事务,即再一次执行start transaction;此时在session2中,再次查询上一条插入语句,发现可查询到上个事务中插入的记录。表名第二次start transaction命令隐式的触发了第一个事务的commit。
4、在事务2中执行另一个insert语句,然后rollback,第二次insert回滚成功。

case 流程view:

Session1:开启事务1,执行插入后再session1中查询该插入记录:
mysql> start transaction;
Query OK, 0 rows affected

mysql> INSERT into tc_trade_bill_history (bill_id,business_id,need_price,real_price,
total_price,bill_status,operate_name) VALUES (33,1,1,1,1,1,1);
Query OK, 1 row affected

mysql> select * from tc_trade_bill_history where bill_id=33;
+---------+---------+-------------+------------+------------+-------------+-------------+--------------+---------
| id | bill_id | business_id | | operate_name | create_time | settle_price |
+---------+---------+-------------+------------+------------+-------------+-------------+--------------+---------
| 1027031 | 33 | 1 | 1 | 1 | 1 |
+---------+---------+-------------+------------+------------+-------------+-------------+--------------+---------
1 row in set

Session2:session2中查询该记录,返回Empty set
mysql> select * from tc_trade_bill_history where bill_id=33;
Empty set

Session1:session1中开启第二个事务,执行完start transaction命令后,在session2中查询上一个事务的插入数据。
mysql> start transaction;
Query OK, 0 rows affected
session2:查询有结果,表名第二个start transaction隐式的执行了事务1的commit操作。
mysql> select * from tc_trade_bill_history where bill_id=33;
+---------+---------+-------------+------------+------------+-------------+-------------+--------------+---------
| id | bill_id | business_id | create_time | settle_price |
+---------+---------+-------------+------------+------------+-------------+-------------+--------------+---------
| 1027031 | 33 | 1 | 1 | 1 | 1 |
+---------+---------+-------------+------------+------------+-------------+-------------+--------------+---------
1 row in set

从mysql的层面可以看出,mysql是不支持嵌套事务的。开启了一个事务的情况下,再开启一个事务2,会隐式的提交上一个事务。假如此时事务2执行失败,再rollback,并不能使得事务1回滚成功(事务2是可以rollback成功的)。同理,如果出现其他业务场景的的事务嵌套,例如先开启t1,然后开启t2,t1、t2包裹在t3中,在执行开启t2时,t1已经被隐式commit了。如果此时t2执行失败,导致t2回滚,外部t3也跟着需要回滚,此时只能保证t2、t3回滚成功,而t1无法回滚。
注:
增加一个autocommit的介绍, autocommit是mysql中的InnoDb数据表特有的语句。(只有在表引擎=InnoDb时,autocommit才会生效)。执行set autocommit = 1; 设置mysql 为自动提交(默认即为1),即将每个sql当做事务执行,sql末尾,默认执行一次commit。但这个设置对有事务的sql就不奏效,事务必须显示的执行commit才可提交事务。

三、Spring事务传播属性(简单介绍,不做重点讨论,详见:Spring事务传播特性的浅析

Spring通过设置事务传播行为来控制多个事务方法相互调用时,事务如何在这些方法间传播。
  • PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到这个事务中。这是最常见的选择。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。以required属性为例
Spring为UserService#logon()方法启动了一个新的事务,而UserSerive#updateLastLogonTime()和UserService#logon()是在相同的类中,没有观察到有事务传播行为的发生,其代码块好像“直接合并”到UserService#logon()中。 然而在执行到ScoreService#addScore()方法时,我们就观察到发生一个事务传播的行为:" Participating in existing transaction ",这说明ScoreService#addScore()添加到UserService#logon()的事务上下文中,两者共享同一个事务所以最终的结果是UserService的logon()、updateLastLogonTime()以及ScoreService的addScore都工作于同一事务中。 

猜你喜欢

转载自blog.csdn.net/daybreak1209/article/details/79956172
今日推荐