Seata AT模式

基本思路

先决条件

  • 支持本地ACID事务的关系数据库。
  • 通过JDBC访问数据库的Java应用程序。

整体机制

从两个阶段提交协议的演变:

  • 阶段1:在同一本地事务中提交业务数据和回滚日志,然后释放本地锁和连接资源。
  • 阶段2:
    • 对于提交情况,异步快速地完成工作。
    • 对于回滚情况,请根据阶段1中创建的回滚日志进行补偿。

写隔离

  • 全局锁,必须犯的阶段1的本地事务之前获取。
  • 如果未获取全局锁,则不应提交本地事务。
  • 如果失败,一个事务将尝试多次获取全局锁,但如果超时,则会发生超时,并回滚本地事务并释放本地锁。

例如:

两个事务tx1和tx2试图更新表a的字段m。m的原始值为1000。

tx1首先开始,开始本地事务,获取本地锁,然后执行更新操作:m = 1000-100 =900。tx1必须在提交本地事务之前获取全局锁,然后再提交本地事务并释放本地锁。

接下来,tx2开始本地事务,获取本地锁,执行更新操作:m = 900-100 =800。在tx2可以提交本地事务之前,它必须获取全局锁,但是全局锁可能由tx1持有,因此tx2会重试。在tx1执行全局提交并释放全局锁之后,tx2可以获取全局锁,然后可以提交本地事务并释放本地锁。

seata_at-1.pnguploading.4e448015.gif正在上传…重新上传取消写隔离:提交

参见上图,tx1在阶段2中执行全局提交并释放全局锁,tx2获取全局锁并提交本地事务。

seata_at-2.pnguploading.4e448015.gif正在上传…重新上传取消写隔离:回滚

参见上图,如果tx1要执行全局回滚,则它必须获取本地锁以还原阶段1的更新操作。

但是,现在本地锁由希望获取全局锁的tx2持有,因此tx1无法回滚,但是它将尝试多次,直到tx2获取全局锁超时,然后tx2回滚本地事务并释放本地锁定后,tx1可以获取本地锁定,并成功执行分支回滚。

因为全局锁在整个过程中都由tx1保持,所以没有写脏问题。

读取隔离

本地数据库的隔离级别被读为commit commit或更高,因此全局事务的默认隔离级别被读为uncommitted

如果当前需要读取全局事务的隔离级别,则Fescar可以通过SELECT FOR UPDATE语句来实现它。

seata_at-3.pnguploading.4e448015.gif转存失败重新上传取消读取隔离:选择更新

全局锁是SELECT FOR UPDATE语句的执行过程中被应用,如果全局锁被其他事务持有,该交易将释放本地锁重试执行SELECT FOR UPDATE语句。在整个过程中,查询将被阻塞,直到获取了全局锁为止,如果获取了锁,则意味着另一个全局事务已提交,因此全局事务的隔离级别被读取为commit

出于性能方面的考虑,Fescar只对SELECT FOR UPDATE做代理工作。对于常规的SELECT语句,什么也不做。

工作过程

以一个例子来说明它。

业务表:product

领域 类型
ID bigint(20) PRI
名称 varchar(100)  
以来 varchar(100)  

AT模式下分支事务的sql:

update product set name = 'GTS' where name = 'TXC';

阶段1

处理:

  1. 解析sql:知道sql类型为更新操作,表名称为product,条件为name ='TXC',依此类推。
  2. 在更新之前查询数据(在图像之前命名):为了找到将要更新的数据,请通过上述where条件生成查询语句。
select id, name, since from product where name = 'TXC';

得到了“之前的图像”:

ID 名称 以来
1个 TXC 2014年
  1. 执行更新sql:更新名称等于“ GTS”的记录。
  2. 更新后查询数据(以图像命名):通过更新前图像数据的主键找到记录。
select id, name, since from product where id = 1;

得到了残像:

ID 名称 以来
1个 GTS 2014年
  1. 插入回滚日志:使用前后图像以及SQL语句相关信息构建回滚日志,然后插入table中UNDO_LOG
{
	"branchId": 641789253,
	"undoItems": [{
		"afterImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "GTS"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"beforeImage": {
			"rows": [{
				"fields": [{
					"name": "id",
					"type": 4,
					"value": 1
				}, {
					"name": "name",
					"type": 12,
					"value": "TXC"
				}, {
					"name": "since",
					"type": 12,
					"value": "2014"
				}]
			}],
			"tableName": "product"
		},
		"sqlType": "UPDATE"
	}],
	"xid": "xid:xxx"
}
  1. 在本地提交之前,该事务将应用提交给TC,以获取表产品中主键等于1的记录的全局锁
  2. 提交本地事务:在同一本地事务中提交PRODUCT表的更新和UNDO_LOG表的插入。
  3. 向TC报告步骤7的结果。

阶段2-回退案例

  1. 从TC收到回滚请求后,开始本地事务,执行以下操作。
  2. 通过XID和分支ID检索UNDO LOG。
  3. 验证数据:将UNDO LOG中更新后的图像数据与当前数据进行比较,如果存在差异,则表示该数据已被当前事务以外的操作所更改,应采用不同的策略进行处理,其他将对此进行详细描述文献。
  4. 基于UNDO LOG中的前映像和业务SQL的相关信息,生成回滚SQL语句。
update product set name = 'TXC' where id = 1;
  1. 提交本地事务,将本地事务的执行结果(分支事务的回滚结果)报告给TC。

阶段2-提交案例

  1. 收到TC的提交请求后,将请求放入工作队列,立即将成功返回TC。
  2. 在队列中执行异步工作的阶段,将以批处理方式删除UNDO LOG。

附录

撤消日志表

UNDO_LOG表:不同数据库的数据类型略有不同。

对于MySQL示例:

领域 类型
branch_id bigint PK
西德 varchar(100)
rollback_info 长毛
log_status tinyint
log_created 约会时间
log_modified 约会时间
分机 varchar(100)
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime NOT NULL COMMENT 'create datetime',
  `log_modified` datetime NOT NULL COMMENT 'modify datetime',
  `ext` varchar(100) DEFAULT NULL COMMENT 'reserved field',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';
发布了103 篇原创文章 · 获赞 1 · 访问量 2359

猜你喜欢

转载自blog.csdn.net/W_317/article/details/104648619