SEATA AT模式原理和不适合场景
下面我用工程视角把 Seata AT(Automatic Transaction)模式的原理完整拆开说明,而不是停留在概念层。
一、AT 模式一句话定义
Seata AT 是一种“基于数据库本地事务 + 自动反向补偿”的分布式事务方案,通过在业务 SQL 执行时自动生成 Undo Log,实现 无侵入、自动回滚、最终一致性中的强一致提交路径。
二、AT 模式的整体架构角色
AT 模式里有三个核心角色:
TM(Transaction Manager) —— 事务发起者
RM(Resource Manager) —— 业务服务(数据库资源)
TC(Transaction Coordinator)—— Seata Server
职责划分非常关键:
- TM:定义全局事务边界(@GlobalTransactional)
- RM:执行业务 SQL,记录 Undo Log
- TC:协调全局事务的提交 / 回滚
三、AT 模式的核心思想(抓住这一点就懂了)
在本地事务提交之前,记录一份“可以把数据恢复到原样”的 Undo Log。
所以 AT 的关键不在“两阶段”,而在 “可逆操作”。
四、AT 模式的完整执行流程(成功路径)
1️⃣ 全局事务开始(TM)
@GlobalTransactional
public void placeOrder() { ... }
- TM 向 TC 注册一个 全局事务 XID
- XID 会通过 RPC 传播到下游服务
2️⃣ 业务 SQL 执行前(RM 拦截)
RM 使用 DataSourceProxy 拦截 SQL:
(1) 查询 Before Image
SELECT * FROM table WHERE id = 1 FOR UPDATE;
- 获取当前行数据
- 加行锁(本地 DB 锁)
(2) 执行业务 SQL
UPDATE table SET balance = balance - 100 WHERE id = 1;
(3) 查询 After Image
SELECT * FROM table WHERE id = 1;
(4) 生成 Undo Log
{
"beforeImage": {...},
"afterImage": {...}
}
3️⃣ 本地事务提交
-
业务数据 + undo_log 在同一个本地事务中提交
-
保证:
- 要么都成功
- 要么都失败
4️⃣ TC 决定全局提交
- 如果所有 RM 都返回成功
- TC 标记全局事务为 Committed
⚠️ AT 模式下:
全局提交阶段什么都不做
(因为本地事务已经提交)
五、AT 模式的回滚流程(失败路径)
1️⃣ 全局事务回滚触发
- 任意 RM 执行失败
- 或 TM 主动抛异常
TC 标记全局事务为 Rollbacking
2️⃣ RM 执行回滚
RM 根据 Undo Log 做:
UPDATE table
SET balance = 1000
WHERE id = 1 AND balance = 900;
⚠️ 注意这个 AND 条件:
- 防止脏写(Dirty Write)
- 如果不匹配 → 回滚失败 → 进入重试
3️⃣ 清理 Undo Log
- 回滚成功后
- 删除对应 undo_log 记录
六、AT 模式为什么能做到“无侵入”
-
不需要改业务 SQL
-
不需要写补偿逻辑
-
通过:
- DataSourceProxy
- SQL 解析
- 自动 Undo Log
这也是 AT 模式最受欢迎的原因。
七、AT 模式的关键技术点(面试 / 评审重点)
1. 全局锁(Global Lock)
-
每个被修改的行都会注册到 TC
-
防止:
- 并发全局事务写同一行
-
代价:
- 降低并发度
2. undo_log 表
-
每个参与 AT 的库都要有
-
存的是:
- Before Image
- After Image
3. 隔离级别要求
- 必须是 READ COMMITTED
- 防止脏读影响 Undo 判断
八、AT 模式的局限(非常重要)
❌ 不适合高并发写
- 全局锁 + 行锁
- TPS 会明显下降
❌ 不支持复杂 SQL
- 子查询
- 批量更新
- 非确定性 SQL(如 NOW())
❌ 强依赖关系型数据库
- 不适合 NoSQL
- 不适合 MQ 场景
九、AT vs TCC / Saga(一句话对比)
| 模式 | 特点 | 适用场景 |
|---|---|---|
| AT | 无侵入,自动回滚 | 低并发、简单业务 |
| TCC | 显式资源锁 | 核心资金链路 |
| Saga | 最终一致 | 长事务、跨系统 |
十、一句话总结
Seata AT 的本质是:用本地事务保证阶段一成功,用 Undo Log 保证阶段二可逆。
如果你愿意,下一步我可以:
- 深入讲 AT 的全局锁实现细节
- 或结合你熟悉的业务(订单 / 库存 / 资金)画一条 完整调用链时序图
简短结论先给出:
复杂 SQL ≠ 一定要用 TCC,但「复杂 SQL + 强一致性 + 跨服务写」时,TCC 通常是更合适的替代方案。
下面我从判定标准、替代方案选择、TCC 是否“过重”三个层次把问题讲清楚。
一、先澄清:什么叫 AT 扛不住的“复杂 SQL”
Seata AT 不能可靠支持的,不是“SQL 写得复杂”,而是 无法被安全反向补偿的 SQL。
典型包括:
1️⃣ 无法构造确定性 Undo 的 SQL
UPDATE ... SET balance = balance - xUPDATE ... SET status = f(status)UPDATE ... SET time = NOW()- 带非确定函数、表达式的更新
2️⃣ 影响行数不可预期
UPDATE ... WHERE status IN (SELECT ...)- 批量更新 / 跨表更新
- join update
3️⃣ Undo Image 体积或成本不可控
- 一次 update 影响上万行
- Undo Log 膨胀严重
👉 这类场景 AT 不是不能跑,而是“跑不稳”。
二、遇到复杂 SQL,有哪几种现实选择
方案一:业务改造,继续用 AT(最优先)
很多“复杂 SQL”其实是可以被拆解的。
示例
UPDATE order SET status = 'PAID' WHERE user_id = ?
可以改为:
SELECT id FROM order WHERE user_id = ?;
UPDATE order SET status = 'PAID' WHERE id = ?;
适合:
- 行数可控
- 性能可接受
方案二:复杂 SQL 所在服务切换为 TCC(常见)
不是整个系统切 TCC,而是“写复杂 SQL 的那个服务”切。
为什么 TCC 合适
-
你自己定义:
- Try:资源预留
- Confirm:正式提交
- Cancel:明确补偿
-
不依赖 SQL 反向推导
示例(库存)
- Try:冻结库存
- Confirm:扣减冻结
- Cancel:释放冻结
👉 复杂度从“SQL 自动逆向”转移到“业务显式建模”。
方案三:Saga(如果不要求强一致)
如果:
- 链路长
- 允许短时间不一致
- 业务可补偿
Saga 通常比 TCC 更轻。
三、什么时候“必须”用 TCC(实战判断表)
你可以用下面这个表快速判断:
| 条件 | AT | TCC |
|---|---|---|
| 简单主键 update | ✔ | ❌ |
| 批量 / join SQL | ❌ | ✔ |
| 非确定性更新 | ❌ | ✔ |
| 高并发写 | ❌ | ✔ |
| 强一致(资金) | ⚠️ | ✔ |
| 开发成本 | 低 | 高 |
经验规则:
只要你不敢在凌晨 3 点相信 Undo Log 能 100% 回滚,那就不要用 AT。
四、TCC 是不是“太重”?为什么生产仍然要用
是的,TCC 重,但重在正确的地方:
1️⃣ 显式资源状态
- 冻结 / 确认 / 取消
- 状态可观察、可修复
2️⃣ 天然支持复杂写逻辑
- 不依赖 SQL 解析
- 不怕批量、不怕表达式
3️⃣ 高并发友好
- 没有 AT 的全局锁
- 并发能力远高于 AT
五、一个非常重要的工程实践(很多团队踩坑)
不要“全局 AT / 全局 TCC”
正确姿势是:
核心资金 / 库存 → TCC
普通业务写 → AT
读多写少 / 长流程 → Saga
混合使用是常态,不是例外。
六、一句话总结
复杂 SQL 本身不是原罪,不能被安全反向补偿才是 AT 的死穴;
当你无法保证 Undo 的可靠性时,TCC 是更确定的工程选择。
如果你愿意,下一步我可以:
- 帮你把一个 AT 场景重构成 TCC(含表设计)
- 或给你一份 AT → TCC 切换的决策清单(架构评审级)

浙公网安备 33010602011771号