Seata 分布式事务原理AT 模式与全局锁摘要在微服务架构中分布式事务是绕不过的难题。Seata 作为阿里巴巴开源的分布式事务解决方案提供了 AT、TCC、SAGA 和 XA 四种模式。本文深入剖析 Seata 1.7.0 的 AT 模式Auto Transaction原理通过源码分析和实战示例揭示其如何通过”自动分支注册”和”全局锁机制”实现高性能的分布式事务一致性。一、分布式事务的挑战与 Seata 架构1.1 分布式事务的困境在单体应用中我们使用 ACID 事务保证数据一致性但在微服务架构中业务被拆分为多个独立服务每个服务拥有独立的数据库传统的本地事务Transactional失效。graph TB subgraph 单体应用 A[业务逻辑] -- B[本地数据库] B -- C[ACID 事务br/一致性保证] end subgraph 微服务架构 D[订单服务br/order_db] -- E{分布式事务br/} F[库存服务br/inventory_db] -- E G[账户服务br/account_db] -- E E -- H[❌ 跨服务一致性br/难以保证] end1.2 Seata 整体架构Seata 采用TAACTransaction As A Coordinator架构包含三个核心组件graph TB subgraph Seata 架构 TC[TCbr/Transaction Coordinatorbr/事务协调器] TM[TMbr/Transaction Managerbr/事务管理器] RM[RMbr/Resource Managerbr/资源管理器] end subgraph 微服务应用 Service1[订单服务] Service2[库存服务] Service3[账户服务] end Service1 --|开启全局事务| TC Service1 --|注册分支事务| TC Service2 --|注册分支事务| TC Service3 --|注册分支事务| TC TC --|全局提交/回滚| Service1 TC --|全局提交/回滚| Service2 TC --|全局提交/回滚| Service3 Service1 -.-|TM/RM| TC Service2 -.-|TM/RM| TC Service3 -.-|TM/RM| TC组件角色职责TC协调者维护全局事务状态驱动全局提交或回滚TM发起者定义全局事务范围发起提交或回滚RM参与者管理分支事务向 TC 注册分支、汇报状态二、AT 模式核心原理2.1 AT 模式 vs TCC vs SAGA vs XA模式一致性性能代码侵入适用场景AT最终一致⭐⭐⭐⭐⭐无GlobalTransactional常见业务基于关系型DBTCC强一致⭐⭐⭐⭐高三阶段接口核心业务性能要求高SAGA最终一致⭐⭐⭐⭐中状态机定义长事务业务流程复杂XA强一致⭐⭐无JTA 标准对性能要求不高的场景2.2 AT 模式两阶段提交AT 模式的核心是”增强型两阶段提交”通过自动生成 undo log 实现无侵入的分布式事务。sequenceDiagram participant TM as TM (事务管理器) participant RM1 as RM (订单服务) participant TC as TC (协调器) participant RM2 as RM (库存服务) participant DB1 as 订单数据库 participant DB2 as 库存数据库 TM-TC: 开始全局事务 (XID) TC--TM: 返回 XID Note over RM1,DB1: 第一阶段业务 SQL 执行 TM-RM1: 执行业务 SQL (update order) RM1-DB1: 查询 before image DB1--RM1: before image 数据 RM1-DB1: 执行业务 SQL DB1--RM1: 执行成功 RM1-DB1: 查询 after image DB1--RM1: after image 数据 RM1-DB1: 插入 undo log DB1--RM1: 插入成功 RM1-DB1: 提交本地事务 DB1--RM1: 提交成功 RM1-TC: 注册分支 (Branch ID) TC--RM1: 注册成功 TM-RM2: 执行业务 SQL (update inventory) RM2-DB2: 查询 before image DB2--RM2: before image 数据 RM2-DB2: 执行业务 SQL DB2--RM2: 执行成功 RM2-DB2: 查询 after image DB2--RM2: after image 数据 RM2-DB2: 插入 undo log DB2--RM2: 插入成功 RM2-DB2: 提交本地事务 DB2--RM2: 提交成功 RM2-TC: 注册分支 (Branch ID) TC--RM2: 注册成功 TM-TC: 提交全局事务 (XID) Note over TC,RM2: 第二阶段异步删除 undo log TC-RM1: 分支提交 RM1-DB1: 删除 undo log TC-RM2: 分支提交 RM2-DB2: 删除 undo log2.3 为什么 AT 模式性能高第一阶段本地提交不锁表不阻塞读写第二阶段异步提交时异步删除 undo log回滚时才并发执行自动生成 undo log无需手动编写补偿逻辑三、源码分析AT 模式实现3.1 DataSource 代理机制源码位置seata-src/sqlparser/seata-sqlparser-core/src/main/java/io/seata/sqlparser/// seata-jdbc/src/main/java/io/seata/rm/datasource/DataSourceProxy.javapublicclassDataSourceProxyextendsAbstractDataSourceProxy{OverridepublicConnectionProxygetConnection()throwsSQLException{// 获取目标数据源连接ConnectiontargetConnectiontargetDataSource.getConnection();// 包装为代理连接returnnewConnectionProxy(targetConnection,this);}}// seata-jdbc/src/main/java/io/seata/rm/datasource/ConnectionProxy.javapublicclassConnectionProxyextendsAbstractConnectionProxy{OverridepublicPreparedStatementprepareStatement(Stringsql)throwsSQLException{// 判断是否为 SELECT 语句if(SQLRecognizerFactory.get(sql).getSQLType()SQLType.SELECT){// SELECT 语句不代理returntargetConnection.prepareStatement(sql);}// INSERT/UPDATE/DELETE 创建 PreparedStatementProxyreturnnewPreparedStatementProxy(targetConnection,sql);}}3.2 SQL 解析与镜像生成源码位置seata-src/core/src/main/java/io/seata/core/context/// seata-rm-datasource/src/main/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutor.javaprotectedTexecuteAutoCommitTrue(Object[]args)throwsThrowable{// 1. 解析 SQL 类型SQLRecognizer recognizerSQLRecognizerFactory.get(statementSQL);// 2. 查询 before imageTableRecords beforeImagebeforeImage();// 3. 执行业务 SQLT resultstatementCallback.execute(statementProxy.getTargetStatement(),args);// 4. 查询 after imageTableRecords afterImageafterImage(beforeImage);// 5. 准备 undo logprepareUndoLog(beforeImage,afterImage);returnresult;}// before image 生成逻辑protectedTableRecordsbeforeImage()throwsSQLException{// 构建查询 SQL主键条件SQLSelectRecognizer visitorSQLVisitorFactory.get(sql,dbType);StringBuilderbeforeImageSqlnewStringBuilder(SELECT * FROM );beforeImageSql.append(tableName).append( WHERE );beforeImageSql.append(visitor.getWhereCondition());// 执行查询PreparedStatementpsttargetConnection.prepareStatement(beforeImageSql.toString());ResultSetrspst.executeQuery();// 构建 TableRecordsreturnTableRecords.buildRecords(rs);}3.3 undo log 结构源码位置seata-rm-datasource/src/main/java/io/seata/rm/datasource/undo/// seata-rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLog.javapublicclassUndoLog{privateLongid;// undo log 主键privateLongbranchId;// 分支事务 IDprivateStringxid;// 全局事务 IDprivateStringcontext;// 序列化的 undo 内容privateIntegerrollbackInfo;// 回滚信息privateLogStatus status;// 状态0:正常1:已完成privateDatecreateTime;// 创建时间}// seata-rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManager.javapublicclassUndoLogManager{// 插入 undo logpublicstaticvoidinsertUndoLog(Stringxid,longbranchId,SQLUndoLog sqlUndoLog){// 1. 序列化 undo logbyte[]undoLogContentserializer.serialize(sqlUndoLog);// 2. 构建 INSERT 语句StringsqlINSERT INTO undo_log (branch_id, xid, context, rollback_info, log_status, created) VALUES (?, ?, ?, ?, ?, now());// 3. 执行插入PreparedStatementpstconnection.prepareStatement(sql);pst.setLong(1,branchId);pst.setString(2,xid);pst.setBytes(3,undoLogContent);pst.setInt(4,0);// LogStatus.Normalpst.executeUpdate();}}四、全局锁机制4.1 为什么需要全局锁问题场景脏写问题sequenceDiagram participant T1 as 全局事务1br/(RM1) participant TC as TC participant T2 as 本地事务2br/(RM1) participant DB as 数据库 Note over T1,DB: 场景全局事务1在第二阶段回滚前本地事务2修改了数据 T1-DB: UPDATE product SET stock 10 (branch_id1) T1-DB: 提交本地事务 (生成 undo log) T1-TC: 注册分支1 T2-DB: UPDATE product SET stock 9 T2-DB: 提交本地事务 (✅ 写入成功) Note over TC: 全局事务1 决定回滚 TC-T1: 分支回滚 T1-DB: 根据 undo log 恢复 (stock 11) Note over DB: ❌ 脏写本地事务2 的修改丢失了解决方案全局锁4.2 全局锁获取流程sequenceDiagram participant RM as RM (分支事务) participant TC as TC (协调器) participant DB as 数据库 Note over RM,DB: 第一阶段提交本地事务前 RM-RM: 执行业务 SQL RM-DB: 插入 undo log RM-DB: 提交本地事务 RM-TC: 注册分支 TC--RM: 注册成功 (branch_id) RM-TC: 申请全局锁 (xid, pk) Note over TC: 检查全局锁表 TC--RM: ✅ 获取成功 Note over RM,DB: 第二阶段全局回滚 TC-RM: 分支回滚指令 RM-DB: 根据 undo log 回滚 RM-TC: 释放全局锁4.3 全局锁源码源码位置seata-rm-datasource/src/main/java/io/seata/rm/datasource/// seata-rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionProxy.javaprivatebooleanacquireLock(StringlockKey){// 1. 构建锁请求LockQueryRequest requestnewLockQueryRequest();request.setXid(xid);request.setLockKey(lockKey);// 2. 向 TC 申请全局锁LockQueryResponse responselockManager.acquireLock(request);// 3. 判断是否获取成功if(response.isLockable()){returntrue;}// 4. 获取失败回滚本地事务thrownewLockConflictException(全局锁获取失败);}// seata-server/src/main/java/io/seata/server/lock/LockManager.javapublicbooleanacquireLock(LockQueryRequest request){Stringxidrequest.getXid();StringlockKeyrequest.getLockKey();// 1. 检查是否已被其他全局事务锁定if(lockStore.isLocked(lockKey)!lockStore.getOwner(lockKey).equals(xid)){returnfalse;// 锁已被占用}// 2. 加锁lockStore.lock(lockKey,xid);returntrue;}4.4 全局锁表结构CREATETABLEdistributed_lock (lock_keyvarchar(128)NOTNULLCOMMENT锁键,xidvarchar(128)NOTNULLCOMMENT全局事务ID,transaction_id bigint(20)DEFAULTNULLCOMMENT事务ID,branch_id bigint(20)NOTNULLCOMMENT分支ID,resource_idvarchar(256)DEFAULTNULLCOMMENT资源ID,table_namevarchar(64)DEFAULTNULLCOMMENT表名,pkvarchar(128)DEFAULTNULLCOMMENT主键,gmt_create datetimeDEFAULTCURRENT_TIMESTAMP,gmt_modified datetimeDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,PRIMARYKEY(lock_key)) ENGINEInnoDBDEFAULTCHARSETutf8mb4;五、实战Spring Boot Seata 实现 AT 模式5.1 业务场景电商下单扣减库存 → 创建订单 → 扣减账户余额// 业务接口GlobalTransactional// Seata 全局事务注解publicvoidpurchase(StringuserId,StringcommodityCode,intorderCount){// 1. 扣减库存storageFeign.deduct(commodityCode,orderCount);// 2. 创建订单orderFeign.create(userId,commodityCode,orderCount);// 3. 扣减余额accountFeign.deduct(userId,orderCount*100);}5.2 依赖配置!-- pom.xml --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactIdversion2022.0.0.0/version/dependencydependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactIdversion1.7.0/version/dependency5.3 数据源代理配置# application.ymlseata:enabled:trueapplication-id:order-servicetx-service-group:my_test_tx_groupservice:vgroup-mapping:my_test_tx_group:defaultregistry:type:nacosnacos:server-addr:127.0.0.1:8848namespace:seatagroup:SEATA_GROUPapplication:seata-serverconfig:type:nacosnacos:server-addr:127.0.0.1:8848namespace:seatagroup:SEATA_GROUP// DataSourceProxyConfig.javaConfigurationpublicclassDataSourceProxyConfig{BeanConfigurationProperties(prefixspring.datasource.druid.master)publicDataSourcedataSource(){DruidDataSource dataSourcenewDruidDataSource();returndataSource;}BeanpublicDataSourceProxydataSourceProxy(DataSourcedataSource){returnnewDataSourceProxy(dataSource);// Seata 数据源代理}BeanpublicSqlSessionFactorysqlSessionFactory(DataSourceProxy dataSourceProxy)throwsException{SqlSessionFactoryBean factoryBeannewSqlSessionFactoryBean();factoryBean.setDataSource(dataSourceProxy);returnfactoryBean.getObject();}}5.4 undo log 表-- 每个业务数据库都需要创建 undo_log 表CREATETABLEundo_log (id bigint(20)NOTNULLAUTO_INCREMENT,branch_id bigint(20)NOTNULL,xidvarchar(100)NOTNULL,contextvarchar(128)NOTNULL,rollback_info longblobNOTNULL,log_statusint(11)NOTNULL,log_created datetimeNOTNULL,log_modified datetimeNOTNULL,extvarchar(100)DEFAULTNULL,PRIMARYKEY(id),UNIQUEKEYux_undo_log (xid,branch_id)) ENGINEInnoDB AUTO_INCREMENT1DEFAULTCHARSETutf8mb4;六、回滚流程与日志清理6.1 全局回滚流程graph TB A[TC 决定回滚] -- B{查找 undo log} B -- C{undo log 状态?} C --|正常| D[反序列化 undo log] C --|已完成| E[跳过已回滚] D -- F[构建回滚 SQL] F -- G[执行回滚] G -- H[更新 undo log 状态] H -- I[删除 undo log异步]回滚 SQL 生成// seata-rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutor.javapublicStringbuildUndoSQL(){// 根据 SQL 类型构建回滚语句switch(sqlType){caseINSERT:// INSERT → DELETEreturnDELETE FROM tableName WHERE buildWhereCondition(afterImage);caseUPDATE:// UPDATE → UPDATE (set before image)returnUPDATE tableName SET buildSetClause(beforeImage) WHERE buildWhereCondition(afterImage);caseDELETE:// DELETE → INSERTreturnINSERT INTO tableName (buildColumns(beforeImage)) VALUES (buildValues(beforeImage));default:thrownewUnsupportedOperationException(不支持的 SQL 类型);}}6.2 日志清理策略策略触发条件实现方式自动清理第二阶段提交成功异步删除 undo log定时清理cron 任务log_status1且超过 7 天手动清理运维操作DELETE FROM undo_log WHERE log_status1七、性能优化与最佳实践7.1 性能调优参数参数默认值推荐值说明client.rm.lock.retryInterval1030全局锁重试间隔client.rm.lock.retryTimes30100全局锁重试次数client.rm.reportSuccessCount51成功上报次数client.rm.asyncCommitBufferLimit100005000异步提交缓冲区大小7.2 避免长事务// ❌ 错误长事务持有全局锁时间过长GlobalTransactionalpublicvoidbadLongTransaction(){// 业务逻辑 1repository.update(data);// 调用外部接口耗时 10 秒externalApi.call();// 业务逻辑 2repository.update(data2);}// ✅ 正确拆分短事务publicvoidgoodShortTransaction(){// 业务逻辑 1transaction1();// 外部接口在事务外externalApi.call();// 业务逻辑 2transaction2();}7.3 全局锁超时配置# seata.confclient {rm {lock {retryInterval 30# 重试间隔retryTimes 100# 重试次数retryPolicyBranchRollbackOnConflict true# 冲突时回滚分支}}}八、版本演进Seata 1.7.0 新特性8.1 优化 undo log 序列化// Seata 1.7.0 引入 Kryo 序列化器替代 JDK 序列化publicclassKryoSerializerimplementsSerializer{OverridepublicTbyte[]serialize(T obj){// Kryo 性能是 JDK 序列化的 10 倍try(Output outputnewOutput(newByteArrayOutputStream())){kryo.writeClassAndObject(output,obj);returnoutput.toBytes();}}}8.2 支持 PostgreSQL 数据库# seata 1.7.0 支持 PostgreSQL AT 模式datasource:url:jdbc:postgresql://localhost:5432/seatadriverClassName:org.postgresql.DriverdbType:postgresql九、总结Seata AT 模式通过以下机制实现高性能分布式事务自动生成 undo log无需手动编写补偿逻辑增强型两阶段提交第一阶段本地提交第二阶段异步全局锁机制防止脏写保证数据一致性轻量级框架无业务代码侵入易于集成适用场景基于关系型数据库的常见业务场景对性能有较高要求。不适用场景非关系型数据库Redis、MongoDB、跨数据库类型事务。参考资料Seata 官方文档https://seata.io/zh-cn/Seata GitHub 源码1.7.0https://github.com/seata/seata/tree/v1.7.0《深入理解 Seata》第3章AT 模式原理标签Seata,分布式事务,AT模式,全局锁,微服务