Seata AT 模式执行流程以及如何避免数据不一致

235 阅读4分钟

Seata AT 模式执行流程

  1. 启动全局事务

    • 业务系统调用 @GlobalTransactional 注解(或显式调用 API),通知事务协调器(TC)启动一个全局事务。
    • TC 分配一个唯一的全局事务 ID,并通知所有分支事务。
  2. 记录 Before Image

    • 在执行业务 SQL 之前,Seata 会代理拦截 SQL 语句,并记录该数据在执行前的快照(称为 Before Image)。
    • Before Image 保存了 SQL 操作前的数据状态,目的是为了在出现问题时,能够回滚到该状态。
  3. 执行业务 SQL

    • 系统执行业务逻辑中的 SQL 语句,完成相应的数据库操作。此时,本地事务仍未提交。
  4. 记录 After Image

    • 在 SQL 执行后,Seata 会记录数据操作后的快照(称为 After Image)。
    • After Image 保存了 SQL 操作后的数据状态,与 Before Image 共同用于事务补偿。
  5. 提交全局事务

    • 当所有的本地事务成功执行,事务协调器(TC)会向所有分支事务发送提交(Commit)请求。
    • 各分支事务接到提交请求后,提交本地事务,完成全局事务的提交。
  6. 回滚全局事务

    • 如果在事务执行过程中,某个分支事务发生异常,事务协调器会发出全局回滚指令。
    • 各分支事务根据 Before Image 生成相应的回滚 SQL,将数据回滚到执行事务之前的状态,确保数据一致性。

这样,Seata 的 AT 模式通过自动生成并执行补偿 SQL,实现了分布式事务的自动提交和回滚。

如何避免数据不一致

在 Seata 的 AT 模式下,本地事务的修改是在执行全局事务时先行生效的。为了避免在全局事务提交前,其他程序对数据库的修改,Seata 使用了**行锁(行级别锁定机制)**来确保数据的隔离性和一致性。具体通过以下几个机制来防止数据冲突:

1. 基于行级锁的保护

  • 在执行业务 SQL 时,Seata 会对涉及的数据行进行行锁保护。
  • 通过代理数据库操作,Seata 会在执行 SQL 时锁定相应的数据行(依赖数据库的行锁机制,例如 MySQL 的 for update),以防止其他事务对这些数据进行并发修改。
  • 这些行锁会持续到全局事务提交或者回滚,确保同一行的数据在事务完成之前不会被其他事务修改。

2. 全局锁

  • Seata 在数据库中引入了“全局锁”的概念,用于标记当前全局事务正在处理的资源。这个锁的粒度可以达到行级别,确保相同资源不能被多个事务并发修改。
  • 在本地事务提交前,Seata 会在 TC 中登记这些全局锁。TC 会在全局事务提交或回滚完成后,释放这些锁。

3. 乐观锁(Optional)

  • 虽然行锁能保证较高的隔离性,但在某些场景下,如果是长事务或者高并发操作,会对系统性能造成较大影响。Seata 也支持采用乐观锁机制,通过比较数据版本号(或时间戳)来避免并发冲突。
  • 当事务提交时,Seata 会检查数据的版本号是否与事务开始时一致,如果一致,则可以提交。如果数据被其他事务修改,版本号不同,则回滚当前事务,确保不会产生数据不一致问题。

4. 脏写保护

  • 当一个事务在修改数据后,还未完成全局事务提交,其他事务通过 Seata 代理访问时,如果发现数据已经被当前事务锁定,其他事务会进入等待状态,直到全局事务提交或回滚。
  • 这种机制确保了脏写的避免,保证没有其他事务能修改尚未被提交的本地事务所涉及的数据。

5. 超时与死锁处理

  • 为了避免由于锁竞争引起的死锁或长时间等待,Seata 提供了全局事务超时控制。如果全局事务在某一时间段内未能提交或者回滚,Seata 会强制回滚该事务,释放锁定的资源。
  • 如果有死锁的情况出现,Seata 也能够自动识别,并中止一些冲突事务以确保系统能继续执行。

通过这些机制,Seata 的 AT 模式可以确保在全局事务完成前,其他程序无法修改该事务已经修改的数据,避免了数据不一致问题。