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 - x
  • UPDATE ... 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 切换的决策清单(架构评审级)
posted @ 2025-12-25 17:31  向着朝阳  阅读(43)  评论(0)    收藏  举报