第
SpringCloudSeata系列:基于AT模式实现分布式事务
以一个示例来说明:
两个全局事务tx1和tx2,分别对a表的m字段进行更新操作,m的初始值1000。
tx1先开始,开启本地事务,拿到本地锁,更新操作m=1000-100=900。本地事务提交前,先拿到该记录的全局锁,本地提交释放本地锁。tx2后开始,开启本地事务,拿到本地锁,更新操作m=900-100=800。本地事务提交前,尝试拿该记录的全局锁,tx1全局提交前,该记录的全局锁被tx1持有,tx2需要重试等待全局锁。
tx1二阶段全局提交,释放全局锁。tx2拿到全局锁提交本地事务。
如果tx1的二阶段全局回滚,则tx1需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果tx2仍在等待该数据的全局锁,同时持有本地锁,则tx1的分支回滚会失败。分支的回滚会一直重试,直到tx2的全局锁等锁超时,放弃全局锁并回滚本地事务释放本地锁,tx1的分支回滚最终成功。
因为整个过程全局锁在tx1结束前一直是被tx1持有的,所以不会发生脏写的问题。
在数据库本地事务隔离级别读已提交(ReadCommitted)或以上的基础上,Seata(AT模式)的默认全局隔离级别是读未提交(ReadUncommitted)。
如果应用在特定场景下,必需要求全局的读已提交,目前Seata的方式是通过SELECTFORUPDATE语句的代理。
SELECTFORUPDATE语句的执行会申请全局锁,如果全局锁被其他事务持有,则释放本地锁(回滚SELECTFORUPDATE语句的本地执行)并重试。这个过程中,查询是被block住的,直到全局锁拿到,即读取的相关数据是已提交的,才返回。
出于总体性能上的考虑,Seata目前的方案并没有对所有SELECT语句都进行代理,仅针对FORUPDATE的SELECT语句。
AT模式的优点:
一阶段完成直接提交事务,释放数据库资源,性能比较好
利用全局锁实现读写隔离
没有代码侵入,框架自动完成回滚和提交
AT模式的缺点:
两阶段之间属于软状态,属于最终一致
框架的快照功能会影响性能,但比XA模式要好很多
AT与XA的区别
简述AT模式与XA模式最大的区别是什么?
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
XA模式强一致;AT模式最终一致
实现AT模式
AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入,因此实现非常简单。
只不过,AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。
1)导入数据库表,记录全局锁
导入undo_log表导入到微服务关联的数据库:
--forATmodeyoumusttoinitthissqlforyoubusinessdatabase.theseataservernotneedit.
CREATETABLEIFNOTEXISTS`undo_log`
`branch_id`BIGINTNOTNULLCOMMENTbranchtransactionid,
`xid`VARCHAR(128)NOTNULLCOMMENTglobaltransactionid,
`context`VARCHAR(128)NOTNULLCOMMENTundo_logcontext,suchasserialization,
`rollback_info`LONGBLOBNOTNULLCOMMENTrollbackinfo,
`log_status`INT(11)NOTNULLCOMMENT0:normalstatus,1:defensestatus,
`log_created`DATETIME(6)NOTNULLCOMMENTcreatedatetime,
`log_modified`DATETIME(6)NOTNULLCOMMENTmodifydatetime