目录
[Seata AT 模式:面试与项目答辩版](#Seata AT 模式:面试与项目答辩版)
[一、30 秒回答:Seata AT 模式是什么](#一、30 秒回答:Seata AT 模式是什么)
[二、TC、TM、RM 分别是什么](#二、TC、TM、RM 分别是什么)
[1. TC:Transaction Coordinator](#1. TC:Transaction Coordinator)
[2. TM:Transaction Manager](#2. TM:Transaction Manager)
[3. RM:Resource Manager](#3. RM:Resource Manager)
[三、XID 的作用](#三、XID 的作用)
[四、XID 怎么跨服务传播](#四、XID 怎么跨服务传播)
[五、AT 模式第一阶段发生了什么](#五、AT 模式第一阶段发生了什么)
[1. 查询前镜像](#1. 查询前镜像)
[2. 执行业务 SQL](#2. 执行业务 SQL)
[3. 查询后镜像](#3. 查询后镜像)
[4. 写入 undo_log](#4. 写入 undo_log)
[5. 注册分支事务和全局锁](#5. 注册分支事务和全局锁)
[6. 本地事务提交](#6. 本地事务提交)
[十、全局锁和 MySQL 行锁有什么区别](#十、全局锁和 MySQL 行锁有什么区别)
[MySQL 行锁](#MySQL 行锁)
[Seata 全局锁](#Seata 全局锁)
[十一、AT 模式的一致性级别](#十一、AT 模式的一致性级别)
[十二、AT 模式的优点](#十二、AT 模式的优点)
[1. 业务侵入低](#1. 业务侵入低)
[2. 性能比 XA 更好](#2. 性能比 XA 更好)
[3. 自动补偿](#3. 自动补偿)
[4. 适合常见关系型数据库场景](#4. 适合常见关系型数据库场景)
[十三、AT 模式的缺点](#十三、AT 模式的缺点)
[1. 依赖数据库](#1. 依赖数据库)
[2. SQL 受限制](#2. SQL 受限制)
[3. 存在中间状态](#3. 存在中间状态)
[4. 热点数据容易产生全局锁冲突](#4. 热点数据容易产生全局锁冲突)
[5. 回滚依赖 undo_log](#5. 回滚依赖 undo_log)
[1. 不要在全局事务里做耗时操作](#1. 不要在全局事务里做耗时操作)
[2. 异常不能被吞掉](#2. 异常不能被吞掉)
[3. 必须设置合理超时时间](#3. 必须设置合理超时时间)
[4. 注意幂等性](#4. 注意幂等性)
[5. Seata Server 不能单点](#5. Seata Server 不能单点)
[6. 监控 undo_log](#6. 监控 undo_log)
[7. 版本尽量统一](#7. 版本尽量统一)
Seata AT 模式:面试与项目答辩版
你现在这个项目已经完整跑通了:
order-service
↓
storage-service
↓
account-service
账户扣款失败后,订单和库存成功回滚;日志中也出现了:
Branch Rollbacking
branch rollback success
PhaseTwo_Rollbacked
rollback status: Rollbacked
说明 Seata 全局事务链路已经生效。
一、30 秒回答:Seata AT 模式是什么
面试时可以这样说:
Seata AT 是一种对业务代码侵入较低的分布式事务方案。业务入口通过
@GlobalTransactional开启全局事务,Seata Server 作为 TC 管理全局事务,参与服务中的 RM 通过数据源代理拦截 SQL,记录数据修改前后的镜像并写入undo_log。第一阶段各分支本地事务直接提交;如果全局事务成功,第二阶段异步删除
undo_log;如果失败,则根据undo_log中的前镜像执行补偿回滚。
核心关键词:
低侵入
数据源代理
前镜像、后镜像
undo_log
全局锁
本地事务一阶段提交
二阶段提交或补偿回滚
二、TC、TM、RM 分别是什么
Seata 中有三个核心角色。
1. TC:Transaction Coordinator
事务协调者,也就是你启动的:
Seata Server
端口:8091
控制台:7091
职责:
创建全局事务
生成 XID
维护全局事务状态
维护分支事务状态
维护全局锁
通知各分支提交或回滚
TC 对应你 seata 数据库中的:
global_table
branch_table
lock_table
distributed_lock
2. TM:Transaction Manager
全局事务管理器。
在你的项目中主要是:
order-service
因为它的方法上加了:
@GlobalTransactional(
name = "create-order",
rollbackFor = Exception.class
)
职责:
向 TC 开启全局事务
获取 XID
业务成功时请求全局提交
业务失败时请求全局回滚
3. RM:Resource Manager
资源管理器,负责管理具体数据库资源。
你的三个服务都是 RM:
order-service
storage-service
account-service
因为它们都操作 seata_test 数据库。
RM 的职责:
拦截业务 SQL
生成前镜像和后镜像
保存 undo_log
向 TC 注册分支事务
申请全局锁
接收 TC 的提交或回滚命令
注意:
order-service = TM + RM
storage-service = RM
account-service = RM
三、XID 的作用
XID 是一笔全局事务的唯一编号。
例如:
10.159.39.99:8091:1189755580561252383
你打印 XID 后,三个服务得到的是同一个值:
order-service XID = 同一个值
storage-service XID = 同一个值
account-service XID = 同一个值
这说明三个服务属于同一笔分布式事务。
关系是:
一个 XID
├── order-service 分支事务
├── storage-service 分支事务
└── account-service 分支事务
每个分支还有自己的:
branch_id
所以:
XID:全局事务编号
branch_id:某个本地分支事务编号
四、XID 怎么跨服务传播
完整过程如下:
1. 请求进入 order-service
2. @GlobalTransactional 被 Seata AOP 拦截
3. order-service 中的 TM 请求 TC 创建全局事务
4. TC 生成并返回 XID
5. XID 绑定到 order-service 当前线程
6. order-service 通过 Feign 调用 storage-service
7. Seata 的 Feign 拦截器把 XID 放入 HTTP 请求头
8. storage-service 的 Seata 拦截器读取 XID
9. XID 绑定到 storage-service 当前线程
10. storage-service 的数据库操作注册为分支事务
11. 调用 account-service 时重复同样过程
12. 三个服务最终归属于同一个 XID
RootContext.getXID() 读取的就是当前线程绑定的 XID。
五、AT 模式第一阶段发生了什么
以库存扣减为例:
UPDATE storage_tbl
SET count = count - 10
WHERE commodity_code = '2002';
Seata 数据源代理会完成以下工作。
1. 查询前镜像
执行 SQL 前先查询:
商品编号:2002
原库存:100
这就是:
Before Image
2. 执行业务 SQL
执行后:
商品编号:2002
新库存:90
3. 查询后镜像
Seata 再查询一次,得到:
After Image
即:
修改前:100
修改后:90
4. 写入 undo_log
Seata 将:
XID
branch_id
前镜像
后镜像
SQL 类型
表名
主键信息
序列化后写入业务库中的:
undo_log
5. 注册分支事务和全局锁
RM 向 TC 注册分支事务:
XID
branch_id
resourceId
lockKey
例如库存行的锁键可能类似:
storage_tbl:主键值
TC 将全局锁信息放入:
lock_table
6. 本地事务提交
业务 SQL 和 undo_log 在同一个本地事务中提交。
这是 AT 模式非常关键的一点:
第一阶段结束后,本地数据库事务已经提交。
因此 AT 模式不会像 XA 一样长时间占用数据库本地事务和本地行锁。
六、第二阶段正常提交
如果三个服务都成功:
订单插入成功
库存扣减成功
账户扣款成功
order-service 方法正常结束,TM 请求 TC 提交全局事务。
第二阶段主要执行:
释放全局锁
异步删除 undo_log
清理 global_table
清理 branch_table
它不需要再次执行订单、库存、账户的业务 SQL,因为这些 SQL 在第一阶段已经提交。
所以 AT 模式的提交阶段通常比较快。
七、第二阶段异常回滚
你这次测试中,账户服务执行:
UPDATE account_tbl
SET money = money - 30000
WHERE user_id = '1002';
触发了数据库约束:
Check constraint 'money_chk_2' is violated
说明扣款会使余额小于允许范围,因此 MySQL 拒绝更新。
之后流程是:
account-service 抛出异常
↓
Feign 返回 HTTP 500
↓
异常传回 order-service
↓
@GlobalTransactional 拦截器感知异常
↓
TM 请求 TC 回滚
↓
TC 通知已经成功的分支执行回滚
↓
订单分支恢复
库存分支恢复
↓
删除 undo_log
释放全局锁
你日志中的:
branch rollback success
PhaseTwo_Rollbacked
rollback status: Rollbacked
就是二阶段回滚成功。
八、为什么已经提交的本地事务还能回滚
这是最常见的面试问题。
答案是:
Seata AT 的"回滚"不是数据库原生事务的
ROLLBACK,而是基于undo_log执行反向补偿 SQL。
例如第一阶段执行:
UPDATE storage_tbl
SET count = 90
WHERE id = 1;
undo_log 中保存前镜像:
count = 100
回滚时,Seata 根据前镜像生成类似的补偿操作:
UPDATE storage_tbl
SET count = 100
WHERE id = 1;
因此它能够恢复已经提交的数据。
九、为什么还需要后镜像
只记录前镜像似乎就能恢复,但 Seata 还需要后镜像,用于检测脏写。
例如:
修改前库存:100
事务修改后:90
但在 Seata 回滚之前,其他程序又把库存改成:
85
此时:
当前数据 85
≠ 后镜像 90
说明业务数据已经被其他操作修改。
如果 Seata直接恢复到 100,就可能覆盖其他事务的合法修改。
因此回滚时会比较:
当前数据
与
undo_log 中的后镜像
如果不一致,说明发生脏写冲突,Seata通常不会直接暴力覆盖,而是进入重试、异常处理或需要人工介入。
十、全局锁和 MySQL 行锁有什么区别
MySQL 行锁
作用范围:
当前数据库实例中的本地事务
生命周期:
本地事务开始
到
本地事务提交或回滚
第一阶段本地事务提交后,MySQL 行锁就释放。
Seata 全局锁
作用范围:
所有加入 Seata 全局事务的业务服务
生命周期:
分支注册
到
全局事务最终提交或回滚
即使第一阶段本地事务已经提交,Seata 全局锁仍然可以继续存在。
作用是防止另一个全局事务同时修改相同业务行。
十一、AT 模式的一致性级别
AT 模式不是严格意义上的数据库强隔离事务。
因为第一阶段本地事务已经提交,其他普通 SQL 查询可能看到中间结果。
例如:
库存已经从 100 扣到 90
但账户扣款尚未完成
此时普通查询可能看到库存为 90。
所以 AT 更准确地说是:
最终一致性
+
全局写隔离
它对全局事务之间的写冲突有全局锁保护,但普通读操作不一定具备严格的全局读隔离。
需要较强读隔离时,可以使用特定的加锁读取,例如:
SELECT ... FOR UPDATE
但这样会增加锁冲突和性能成本。
十二、AT 模式的优点
1. 业务侵入低
业务入口只需要:
@GlobalTransactional
下游普通数据库操作可以基本保持原样。
不需要手写:
Try
Confirm
Cancel
2. 性能比 XA 更好
第一阶段本地事务直接提交,不长时间占用数据库本地锁。
3. 自动补偿
通过 undo_log 自动完成反向恢复,不需要业务开发者为每个操作手写补偿方法。
4. 适合常见关系型数据库场景
特别适合:
订单
库存
账户
优惠券
积分
这类多个服务共同修改关系型数据库的业务。
十三、AT 模式的缺点
1. 依赖数据库
AT 主要围绕关系型数据库和 SQL 数据源代理工作。
如果事务中包含:
Redis
Elasticsearch
消息队列
文件系统
外部支付接口
第三方 HTTP 接口
这些资源不能简单通过 AT 的 undo_log 自动恢复。
2. SQL 受限制
复杂 SQL、存储过程、部分批处理 SQL、数据库特有语法,可能无法被 Seata正确解析和生成镜像。
3. 存在中间状态
第一阶段本地事务已经提交,因此其他普通查询可能看到全局事务尚未完成的中间数据。
4. 热点数据容易产生全局锁冲突
例如大量请求同时扣减同一个商品库存:
storage_tbl:商品1001
多个事务都会竞争相同的全局锁,可能出现:
LockConflictException
获取全局锁失败
重试
超时
5. 回滚依赖 undo_log
如果:
undo_log 被误删
表结构发生变化
回滚信息损坏
数据库连接中断
都可能导致自动回滚失败。
十四、生产环境最重要的风险
1. 不要在全局事务里做耗时操作
错误示例:
@GlobalTransactional
public void createOrder() {
insertOrder();
callThirdPartyPayment();
sendEmail();
uploadFile();
Thread.sleep(30_000);
deductStock();
}
事务越长:
全局锁持有越久
冲突概率越高
超时概率越高
undo_log 保留越久
TC 压力越大
全局事务中只放必要的数据库操作。
2. 异常不能被吞掉
错误:
try {
accountApi.deduct(...);
} catch (Exception e) {
log.error("扣款失败", e);
}
return orderId;
虽然扣款失败,但方法最终正常返回,TM 可能请求全局提交。
正确:
try {
accountApi.deduct(...);
} catch (Exception e) {
throw new RuntimeException("扣款失败", e);
}
3. 必须设置合理超时时间
如果事务执行时间超过 TC 配置的超时时间,可能触发超时回滚。
不能盲目把超时改得特别大,否则会让全局锁长期占用。
4. 注意幂等性
TC、RM、网络通信可能存在重试。
业务接口应考虑:
重复下单
重复扣款
重复扣库存
通常通过:
业务唯一号
唯一索引
状态机
幂等表
请求令牌
避免重复执行。
5. Seata Server 不能单点
你现在本地只有一个 TC:
127.0.0.1:8091
生产环境如果 TC 宕机:
无法开启新全局事务
无法提交或回滚
分支注册失败
生产环境通常需要:
多节点 Seata Server
注册到 Nacos
数据库持久化
负载均衡
健康检查
监控告警
6. 监控 undo_log
重点监控:
undo_log 数量持续增长
大量 rollback 失败
大量全局锁冲突
长时间未完成事务
TC 数据库慢 SQL
如果 undo_log 长期不清理,通常说明:
全局事务没有正常结束
TC 通信异常
二阶段任务异常
回滚失败
7. 版本尽量统一
你当前日志中可以看到:
Seata Server:2.2.0
客户端 seata-all:2.0.0
客户端目前已经能与服务端通信并完成回滚,说明当前组合可以运行;但生产环境仍建议尽量使用经过兼容性验证的版本组合,减少协议、配置项和序列化兼容风险。
十五、项目答辩标准话术
面试官问:
你的项目为什么要使用 Seata?
可以回答:
项目的下单流程涉及订单、库存和账户三个服务。普通的
@Transactional只能保证单个服务、单个数据源内的本地事务,无法覆盖 Feign 远程调用。因此我在订单服务的业务编排方法上使用
@GlobalTransactional,由订单服务作为 TM 开启全局事务,库存、账户以及订单服务的数据源作为 RM 注册分支事务,Seata Server 作为 TC 统一协调提交或回滚。
面试官问:
你怎么证明 Seata 真正生效了?
可以回答:
我做了正常提交和异常回滚两组测试。异常测试中故意让账户扣款违反金额非负约束,账户服务返回 500,异常通过 Feign 传回订单服务。订单服务日志出现
will be rollback、Branch Rollbacked、PhaseTwo_Rollbacked和rollback status: Rollbacked。最终数据库中订单没有新增、库存恢复、余额不变,说明三个分支事务完成了全局回滚。
面试官问:
AT 模式为什么能回滚已经提交的数据?
回答:
AT 第一阶段会通过数据源代理记录业务数据修改前后的镜像,并将这些镜像保存到
undo_log。本地事务在第一阶段直接提交。全局事务失败后,RM 根据后镜像检查数据是否被其他事务修改,再根据前镜像生成补偿 SQL,恢复业务数据。
面试官问:
AT 和 XA 有什么区别?
回答:
XA 第一阶段通常会保持数据库资源和锁,等待事务协调者最终决定,因此强一致性更强,但资源占用和性能成本较高。AT 第一阶段本地事务直接提交,通过全局锁和
undo_log实现最终一致性,性能更好、侵入更低,但会存在短暂中间状态,也依赖 SQL 解析和补偿机制。
十六、你现在应该真正记住的主线
@GlobalTransactional
↓
TM 向 TC 开启全局事务
↓
TC 返回 XID
↓
XID 通过 Feign 传播
↓
各 RM 注册分支事务
↓
数据源代理记录前后镜像
↓
写业务数据和 undo_log
↓
第一阶段本地提交
↓
成功:删除 undo_log、释放全局锁
失败:读取 undo_log、执行补偿回滚
这条链路能够完整讲出来,Seata AT 模式的面试问题基本就能应对。