AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。
Seata的AT模型
流程梳理
我们用一个真实的业务来梳理下AT模式的原理。
比如,现在有一个数据库表,记录用户余额。
id | money |
---|---|
1 | 100 |
其中一个分支业务要执行的SQL为
update tb_account set money = money - 10 where id = 1
AT模式下,当前分支事务执行流程如下:
一阶段:
- TM发起并注册全局事务到TC
- TM调用分支事务
- 分支事务准备执行业务SQL
- RM拦截业务SQL,根据where条件查询原始数据,形成快照。
- RM执行业务SQL,提交本地事务,释放数据库锁。此时
money = 90
- RM报告本地事务状态给TC
二阶段:
- TM通知TC事务结束
- TC检查分支事务状态
- 如果都成功,则立即删除快照
- 如果有分支事务失败,需要回滚。读取快照数据,将快照恢复到数据库。此时数据库再次恢复为100
脏写问题
在多线程并发访问AT模式的分布式事务时,有可能出现脏写问题,如图:
解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
AT与XA的区别
- XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
- XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
-
AT与XA的优缺点
AT模式的优点:
一阶段完成直接提交事务,释放数据库资源,性能比较好
- 利用全局锁实现读写隔离
- 没有代码侵入,框架自动完成回滚和提交
AT模式的缺点:
- 两阶段之间属于软状态,属于最终一致
- 框架的快照功能会影响性能,但比XA模式要好很多
实现AT模式
AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入,因此实现非常简单。
只不过,AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。
- 创建数据库表记录快照和全局锁 ```sql SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;
— 数据快照表,导入到我们写的微服务相关的数据库里面,如果微服务用了多个数据库,每个数据库都创一个这个undo_log表
DROP TABLE IF EXISTS undo_log
;
CREATE TABLE undo_log
(
branch_id
bigint(20) NOT NULL COMMENT ‘branch transaction id’,
xid
varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT ‘global transaction id’,
context
varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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(6) NOT NULL COMMENT ‘create datetime’,
log_modified
datetime(6) NOT NULL COMMENT ‘modify datetime’,
UNIQUE INDEX ux_undo_log
(xid
, branch_id
) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = ‘AT transaction mode undo table’ ROW_FORMAT = Compact;
— 全局锁表,lock_table导入到TC服务关联的数据库,tc服务启动之前的时候创建了一个数据库
DROP TABLE IF EXISTS lock_table
;
CREATE TABLE lock_table
(
row_key
varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
xid
varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
transaction_id
bigint(20) NULL DEFAULT NULL,
branch_id
bigint(20) NOT NULL,
resource_id
varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
table_name
varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
pk
varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
gmt_create
datetime NULL DEFAULT NULL,
gmt_modified
datetime NULL DEFAULT NULL,
PRIMARY KEY (row_key
) USING BTREE,
INDEX idx_branch_id
(branch_id
) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
2. 修改配置文件
```yaml
seata:
data-source-proxy-mode: AT # 默认就是AT