问题

AT模式实际上也是两阶段提交的一种模式

答案

1. 核心概念

AT模式(Automatic Transaction)是Seata最主流的事务模式,是一种改进的两阶段提交,也被称为自动化的TCC

核心思想

  • 基于数据快照SQL解析实现自动补偿
  • 一阶段直接提交本地事务(释放锁)
  • 二阶段通过回滚日志进行补偿(如果需要回滚)

与传统2PC的区别

  • 传统2PC:一阶段锁定资源,等待二阶段提交
  • AT模式:一阶段直接提交,二阶段异步处理

2. AT模式的执行流程

角色说明

TC (Transaction Coordinator)     事务协调器
  ├─ TM (Transaction Manager)    事务管理器(发起全局事务)
  └─ RM (Resource Manager)       资源管理器(管理分支事务)

成功场景(完整流程)

业务服务(TM)        TC服务器          订单服务(RM)      库存服务(RM)
    |                    |                   |                   |
    |--1.开启全局事务--> |                   |                   |
    |<--返回XID----------|                   |                   |
    |                    |                   |                   |
    |--2.调用订单服务--------------------------->                 |
    |                    |<--3.注册分支------|                   |
    |                    |                   |                   |
    |                    |               [4.执行SQL]             |
    |                    |               [5.记录回滚日志]        |
    |                    |               [6.提交本地事务]        |
    |                    |               [7.释放锁]              |
    |                    |                   |                   |
    |<--8.订单创建成功----------------------|                   |
    |                    |                   |                   |
    |--9.调用库存服务------------------------------------------------>
    |                    |<--10.注册分支-------------------------|
    |                    |                                   [执行SQL]
    |                    |                                   [记录回滚日志]
    |                    |                                   [提交本地事务]
    |                    |                                   [释放锁]
    |<--11.库存扣减成功--------------------------------------------|
    |                    |                   |                   |
    |--12.提交全局事务-→ |                   |                   |
    |                    |--13.通知分支提交->|                   |
    |                    |                   [删除回滚日志]      |
    |                    |--14.通知分支提交---------------------->
    |                    |                                   [删除回滚日志]

失败场景(回滚流程)

业务服务(TM)        TC服务器          订单服务(RM)      库存服务(RM)
    |                    |                   |                   |
    |--1.开启全局事务--> |                   |                   |
    |--2.订单服务--------|------------------>|                   |
    |                    |               [提交本地事务] ✓        |
    |--3.库存服务--------|---------------------------------->   |
    |                    |                               [失败] ✗
    |                    |                   |                   |
    |--4.回滚全局事务--> |                   |                   |
    |                    |--5.通知分支回滚-->|                   |
    |                    |               [使用回滚日志]          |
    |                    |               [生成反向SQL]           |
    |                    |               [执行补偿]              |
    |                    |               [删除回滚日志]          |

3. AT模式的核心机制

机制1:数据快照(Undo Log)

原理:在执行业务SQL前后,记录数据的前镜像和后镜像。

// 业务代码
@GlobalTransactional
public void updateAccount(String accountId, BigDecimal amount) {
    // 执行业务SQL
    accountMapper.update(
        "UPDATE account SET balance = balance - ? WHERE id = ?",
        amount, accountId
    );
}

// Seata自动生成的Undo Log
{
    "branchId": 123456,
    "xid": "global-tx-001",
    "tableName": "account",
    "beforeImage": {  // 前镜像
        "rows": [{
            "fields": [
                {"name": "id", "value": "A001"},
                {"name": "balance", "value": 1000.00},
                {"name": "version", "value": 1}
            ]
        }]
    },
    "afterImage": {  // 后镜像
        "rows": [{
            "fields": [
                {"name": "id", "value": "A001"},
                {"name": "balance", "value": 900.00},
                {"name": "version", "value": 2}
            ]
        }]
    },
    "sqlType": "UPDATE"
}

Undo Log表结构

CREATE TABLE undo_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    branch_id BIGINT NOT NULL,
    xid VARCHAR(100) NOT NULL,
    context VARCHAR(128) NOT NULL,
    rollback_info LONGBLOB NOT NULL,  -- 序列化的回滚日志
    log_status INT NOT NULL,
    log_created DATETIME NOT NULL,
    log_modified DATETIME NOT NULL,
    UNIQUE KEY ux_undo_log (xid, branch_id)
);

机制2:全局锁

目的:保证分布式事务的隔离性。

场景:两个全局事务同时修改同一行数据

事务1                          全局锁                       事务2
  |                              |                            |
  |--更新 account.id=A001----    |                            |
  |  (本地事务已提交)             |                            |
  |--获取全局锁----------------> |                            |
  |                          [锁定A001]                       |
  |                              |                            |
  |                              |  <--尝试获取全局锁---------|
  |                              |      (等待...)             |
  |                              |                            |
  |--提交全局事务--------------> |                            |
  |                          [释放A001]                       |
  |                              |  [获取A001成功]---------->|
  |                              |                            |

全局锁实现

-- 全局锁表
CREATE TABLE lock_table (
    row_key VARCHAR(128) PRIMARY KEY,    -- 表名:主键值
    xid VARCHAR(100) NOT NULL,           -- 全局事务ID
    transaction_id BIGINT NOT NULL,
    branch_id BIGINT NOT NULL,
    resource_id VARCHAR(256) NOT NULL,
    table_name VARCHAR(64) NOT NULL,
    pk VARCHAR(128) NOT NULL,
    gmt_create DATETIME NOT NULL,
    gmt_modified DATETIME NOT NULL,
    INDEX idx_xid (xid)
);

-- 例如:account:A001 被事务 global-tx-001 锁定
INSERT INTO lock_table (row_key, xid, ...) 
VALUES ('account:A001', 'global-tx-001', ...);

获取全局锁的流程

// 分支事务提交前
public void commitBranchTransaction(BranchTransaction branch) {
    // 1. 提交本地事务(释放本地锁)
    localTransaction.commit();
    
    // 2. 向TC注册分支
    tc.branchRegister(branch);
    
    // 3. 获取全局锁(可重试)
    boolean locked = false;
    int retry = 0;
    while (!locked && retry < MAX_RETRY) {
        locked = tc.acquireGlobalLock(branch);
        if (!locked) {
            Thread.sleep(10); // 等待重试
            retry++;
        }
    }
    
    if (!locked) {
        // 获取全局锁失败,回滚本地事务
        rollbackLocalTransaction(branch);
        throw new GlobalLockException();
    }
}

机制3:自动补偿

回滚时的补偿流程

// Seata自动生成的补偿逻辑
public void rollback(UndoLog undoLog) {
    // 1. 查询当前数据(校验数据一致性)
    Row currentRow = selectByPk(undoLog.getTableName(), undoLog.getPk());
    
    // 2. 数据校验
    Row afterImage = undoLog.getAfterImage();
    if (!currentRow.equals(afterImage)) {
        // 数据已被修改,可能是脏写
        throw new DataDirtyException("Data has been modified by another transaction");
    }
    
    // 3. 生成反向SQL
    Row beforeImage = undoLog.getBeforeImage();
    String reverseSql = generateReverseSql(undoLog.getSqlType(), beforeImage);
    // 例如:UPDATE account SET balance = 1000.00, version = 1 WHERE id = 'A001'
    
    // 4. 执行补偿
    executeUpdate(reverseSql);
    
    // 5. 删除Undo Log
    deleteUndoLog(undoLog.getBranchId(), undoLog.getXid());
}

4. AT模式 vs 传统2PC

对比表格

维度 传统2PC AT模式
锁定时间 一阶段锁定到二阶段 一阶段即释放
性能 差(同步阻塞) 好(异步处理)
代码侵入 低(数据库层面) 低(透明)
隔离性 强(资源锁定) 弱(依赖全局锁)
数据一致性 强一致 最终一致
补偿方式 数据库回滚 自动生成反向SQL

时序对比

传统2PC

时间轴:
t0  ─→ Prepare(锁定资源)
       ↓
       [资源锁定期间,其他事务等待]
       ↓
t10 ─→ Commit(释放资源)

锁定时间:10个时间单位

AT模式

时间轴:
t0  ─→ 执行SQL + 记录Undo Log
t1  ─→ 提交本地事务(释放本地锁)
t2  ─→ 获取全局锁
       ↓
       [只有全局锁,本地锁已释放]
       ↓
t5  ─→ 全局提交(释放全局锁)

本地锁定时间:1个时间单位
全局锁定时间:3个时间单位(但不阻塞本地事务)

5. AT模式的完整代码示例

业务代码(无侵入)

// 主业务服务
@Service
public class BusinessService {
    
    @Autowired
    private OrderService orderService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private AccountService accountService;
    
    /**
     * 创建订单(全局事务)
     */
    @GlobalTransactional(timeoutMills = 300000, name = "create-order")
    public void createOrder(OrderDTO orderDTO) {
        // 1. 创建订单(分支事务1)
        orderService.createOrder(orderDTO);
        
        // 2. 扣减库存(分支事务2)
        inventoryService.deductStock(
            orderDTO.getProductId(), 
            orderDTO.getQuantity()
        );
        
        // 3. 扣减余额(分支事务3)
        accountService.deductBalance(
            orderDTO.getAccountId(), 
            orderDTO.getAmount()
        );
        
        // Seata自动管理:
        // - 任一服务失败,自动回滚所有分支
        // - 全部成功,自动提交所有分支
    }
}

// 订单服务
@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 创建订单(普通业务代码,无需修改)
     */
    @Transactional
    public void createOrder(OrderDTO orderDTO) {
        Order order = new Order();
        order.setId(orderDTO.getOrderId());
        order.setUserId(orderDTO.getUserId());
        order.setAmount(orderDTO.getAmount());
        
        // 普通的CRUD操作
        orderMapper.insert(order);
        
        // Seata在背后做了:
        // 1. 拦截SQL
        // 2. 查询前镜像
        // 3. 执行业务SQL
        // 4. 查询后镜像
        // 5. 生成Undo Log
        // 6. 提交本地事务
        // 7. 注册分支到TC
        // 8. 上报分支状态
    }
}

// 库存服务
@Service
public class InventoryService {
    
    @Autowired
    private InventoryMapper inventoryMapper;
    
    @Transactional
    public void deductStock(String productId, int quantity) {
        // 普通的业务代码
        Inventory inventory = inventoryMapper.selectById(productId);
        
        if (inventory.getStock() < quantity) {
            throw new InsufficientStockException("库存不足");
        }
        
        inventory.setStock(inventory.getStock() - quantity);
        inventoryMapper.updateById(inventory);
        
        // Seata自动处理分布式事务
    }
}

// 账户服务
@Service
public class AccountService {
    
    @Autowired
    private AccountMapper accountMapper;
    
    @Transactional
    public void deductBalance(String accountId, BigDecimal amount) {
        Account account = accountMapper.selectById(accountId);
        
        if (account.getBalance().compareTo(amount) < 0) {
            throw new InsufficientBalanceException("余额不足");
            // 抛出异常后,Seata会自动回滚所有分支
        }
        
        account.setBalance(account.getBalance().subtract(amount));
        accountMapper.updateById(account);
    }
}

配置文件

# application.yml
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_tx_group
  
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP
      
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace: seata
      group: SEATA_GROUP

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order?useSSL=false
    username: root
    password: root

数据库准备

-- 1. 业务表
CREATE TABLE `order` (
    id VARCHAR(64) PRIMARY KEY,
    user_id VARCHAR(64) NOT NULL,
    product_id VARCHAR(64) NOT NULL,
    quantity INT NOT NULL,
    amount DECIMAL(20, 2) NOT NULL,
    create_time DATETIME NOT NULL
);

-- 2. Undo Log表(每个业务库都需要)
CREATE TABLE undo_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    branch_id BIGINT NOT NULL,
    xid VARCHAR(100) NOT NULL,
    context VARCHAR(128) NOT NULL,
    rollback_info LONGBLOB NOT NULL,
    log_status INT NOT NULL,
    log_created DATETIME NOT NULL,
    log_modified DATETIME NOT NULL,
    UNIQUE KEY ux_undo_log (xid, branch_id)
);

6. AT模式的优缺点

优点

优点1:性能好

对比2PC:
- 本地事务立即提交,不阻塞
- 资源锁定时间短
- 吞吐量高

优点2:无代码侵入

// 无需像TCC那样实现Try、Confirm、Cancel
// 只需加@GlobalTransactional注解
@GlobalTransactional
public void businessMethod() {
    // 正常的业务代码
}

优点3:自动补偿

不需要手写补偿逻辑
Seata自动:
1. 记录数据快照
2. 生成反向SQL
3. 执行补偿操作

缺点

缺点1:不保证隔离性

场景:全局事务未提交前,本地事务已提交

事务A(全局事务):
  t1: 扣减余额100(本地事务提交)
  t2: 其他操作...
  t3: 全局事务回滚

事务B(普通事务):
  t2: 读到扣减后的余额(脏读)

解决:
- 使用@GlobalLock注解
- 或业务层面避免

缺点2:依赖回滚日志

- 需要额外的undo_log表
- 增加存储开销
- 回滚日志可能被误删

缺点3:SQL解析限制

- 不支持所有SQL语法
- 复杂SQL可能解析失败
- 批量操作性能较差

7. AT模式 vs TCC

维度 AT模式 TCC
实现复杂度 低(自动化) 高(手动实现三个方法)
代码侵入 低(加注解) 高(改业务代码)
性能
资源锁定 短(依赖全局锁) 短(业务预留)
隔离性
补偿方式 自动生成反向SQL 手动编写Cancel
适用场景 通用场景 需要强隔离性

8. 总结

AT模式核心要点

  • 是一种改进的两阶段提交
  • 一阶段直接提交本地事务(释放锁)
  • 通过数据快照和全局锁实现分布式事务
  • 二阶段异步处理(提交或回滚)

与传统2PC的区别

  • 锁定时间更短(性能更好)
  • 补偿方式不同(自动生成反向SQL)
  • 代码无侵入(透明化)

适用场景

  • 大多数分布式事务场景
  • 对性能有要求
  • 不希望修改业务代码
  • 能接受弱隔离性

面试要点

  • 能清晰描述AT模式的两阶段流程
  • 理解数据快照和全局锁的作用
  • 掌握AT模式与2PC、TCC的区别
  • 了解AT模式的优缺点和适用场景