简单聊聊事务补偿机制
假设有如下的业务流程,用户1给用户2转账100元:
转账服务需要执行如下操作:
第1步. 在数据库连接1上执行:update 用户表 set (用户1的余额) = (用户1的余额)- 100;
第2步. 在数据库连接2上执行:update 用户表 set (用户2的余额) = (用户2的余额)+ 100;
可能的问题:
1:第1步操作过程中,数据库1挂了,转账服务无法得知对用户1的扣款操作是否成功;
2:第1步操作成功,第2步操作失败,转账服务回滚第1步的操作时,数据库1挂了;
3:第1步操作成功,第2步操作过程中,数据库2挂了,转账服务无法得知是否成功给用户2加了钱;
基于上面的问题,产生了如下的数据库设计:
转账流程变成了如下步骤:
第1步:
转账服务生成一个事务号,全局唯一;
第2步:转账服务在数据库1上执行事务:
开始事务:
update 用户表 set (用户1的余额) = (用户1的余额)- 100;
insert 事务表 (事务号,成功)
结束事务:
第3步:转账服务在数据库2上执行事务:
开始事务:
update 用户表 set (用户2的余额) = (用户2的余额)+ 100;
insert 事务表 (事务号,成功)
结束事务:
这样做的好处
当操作用户1的账户失败时,转账服务可以通过再次查询数据库1的事务表来判断操作是否成功;
当操作用户2的账户失败时,转账服务可以通过再次查询数据库2的事务表来判断操作是否成功;
接下来的问题:
当转账服务更新用户1的账户成功后,接下来转账服务更新用户2的账户之前,转账服务自己挂了;
这时,用户1被扣了100,但是用户2没多出来100,数据不一致;
新的数据库设计产生了,如下:
接下来的操作步骤变成了这样:
转账服务的操作:
第1步:生成全局唯一事务号;生成事务号对应的时间戳;
第2步:在回滚库的日志表中插入---“事务号开始”的操作;
第3步:在回滚库的日志表插入---“扣除用户1的账户100元“的操作;
第4步:在数据库1上执行事务:
开始事务:
update 用户表 set (用户1的余额) = (用户1的余额)- 100;
insert 事务表 (事务号,成功)
结束事务:
第5步:在回滚库的日志表插入---“增加用户2的账户100元”的操作;
第6步:在数据库2上执行事务:
开始事务:
update 用户表 set (用户2的余额) = (用户2的余额)+ 100;
insert 事务表 (事务号,成功)
结束事务:
第7步:在回滚库的日志表插入---“事务号结束”的操作;
回滚服务的操作:
假设转账超时时间是1小时;
定期检查回滚库中的回滚日志表;
如果事务号对应结束,则忽略;
如果事务号没有结束,但是事务没超时,也忽略;
如果事务号没有结束,事务超时,则按照回滚日志,反向操作,对事务进行补偿,补偿步骤如下:
第1步:对用户2进行事务补偿,检查数据库2的用户2的事务是否成功;
第2步:如果成功,则认为事务完成,在事务回滚日志表中将这次事务标识为成功;并跳到“结束步骤”;
第3步:对用户1进行事务补偿,检查数据库1的用户1的事务是否成功;
第4步:如果成功,则执行如下事务:
开始事务
update 用户表 set (用户1的余额) = (用户1的余额)+ 100;
update 事务表 (事务号,回滚成功);
结束事务
第5步:在事务回滚日志表将这次事务标识为成功;
结束步骤
结束哈;