【Seata分布式事务源码分析】
文章目录
- Seata全流程操作
- Seata自动和装配
- TM获取xid源码分析
- RM注册,生成undolog
- TC的启动和接收请求流程
- Seata常见的异常场景
Seata全流程操作
TM Transaction Manager 发起/结束全局事务
TC Transaction Coordinator 管理全局事务状态,通知 RM 提交/回滚
RM Resource Manager 管理分支事务(本地数据库),执行提交/回滚
分布式事务的操作流程:
- TM 发起全局事务(入口处被代理拦截)
注解:@GlobalTransactional
TM 向 TC 注册事务,TC 生成全局事务 XID - 每个业务 SQL 被代理执行,生成分支事务(RM)
拦截 insert/update/delete SQL
获取 before image
执行业务 SQL
获取 after image
构建 undo_log,插入 undo_log 表
向 TC 注册分支事务 - 如果业务成功,TM 提交全局事务
TC 更新全局事务状态为 Committing
通知所有 RM 执行 分支提交
删除 undo_log(AT 模式下) - 如果业务异常或主动回滚,TM 回滚事务
TC 更新事务状态为 Rollbacking
通知所有 RM 执行 分支回滚
RM 查找 undo_log,还原数据
执行 rollback SQL 并删除 undo_log
Seata自动和装配
导包:seata-spring-boot-starter
自动装配的对象:
GlobalTransactionScanner:初始化TM和RM客户端
项目启动时,就会执行此方法:
public void afterPropertiesSet() {//判断全局事务是否开启,没开启,执行下面的逻辑if (disableGlobalTransaction) {if (LOGGER.isInfoEnabled()) {LOGGER.info("Global transaction is disabled.");}ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (CachedConfigurationChangeListener) this);return;}//全局事务开启,CAS将false改为trueif (initialized.compareAndSet(false, true)) {//初始化客户端initClient();}this.findBusinessBeanNamesNeededEnhancement();}
初始化客户端:
protected void initClient() {if (LOGGER.isInfoEnabled()) {LOGGER.info("Initializing Global Transaction Clients ... ");}if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " +"please change your default configuration as soon as possible " +"and we don't recommend you to use default tx-service-group's value provided by seata",DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);}//健壮性校验if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));}//初始化TM客户端TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);if (LOGGER.isInfoEnabled()) {LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);}//初始化RM客户端RMClient.init(applicationId, txServiceGroup);if (LOGGER.isInfoEnabled()) {LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);}if (LOGGER.isInfoEnabled()) {LOGGER.info("Global Transaction Clients are initialized. ");}//注册自动以的钩子函数registerSpringShutdownHook();}
findBusinessBeanNamesNeededEnhancement:找出当前 Spring 容器中需要 AOP 增强的业务类 Bean 名,并保存到 NEED_ENHANCE_BEAN_NAME_SET 中。
private void findBusinessBeanNamesNeededEnhancement() {if (applicationContext instanceof ConfigurableApplicationContext) {ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;ConfigurableListableBeanFactory configurableListableBeanFactory = configurableApplicationContext.getBeanFactory();//获取所有的beanFactory的对象String[] beanNames = applicationContext.getBeanDefinitionNames();//遍历每一个对象for (String contextBeanName : beanNames) {//获取BeanDefinitionBeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(contextBeanName);if (StringUtils.isBlank(beanDefinition.getBeanClassName())) {continue;}if (IGNORE_ENHANCE_CHECK_SET.contains(beanDefinition.getBeanClassName())) {continue;}try {// 反射获取类Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName());//通过 DefaultInterfaceParser 分析类,判断:是否标注了 @GlobalTransactional是否是实现了 TCC 的 TwoPhaseBusinessAction 接口是否 Dubbo/Sofa/HSF 的 ServiceBeanIfNeedEnhanceBean ifNeedEnhanceBean = DefaultInterfaceParser.get().parseIfNeedEnhancement(beanClass);//没有被代理,直接跳过if (!ifNeedEnhanceBean.isIfNeed()) {continue;}if (ifNeedEnhanceBean.getNeedEnhanceEnum().equals(NeedEnhanceEnum.SERVICE_BEAN)) {// 如果是 Dubbo/Sofa/HSF ServiceBean,处理被引用的业务类:PropertyValue propertyValue = beanDefinition.getPropertyValues().getPropertyValue("ref");if (propertyValue == null) {// the native bean which HSF service bean referencedpropertyValue = beanDefinition.getPropertyValues().getPropertyValue("target");}if (propertyValue != null) {RuntimeBeanReference r = (RuntimeBeanReference) propertyValue.getValue();if (r != null && StringUtils.isNotBlank(r.getBeanName())) {//加入增强 Bean 集合:NEED_ENHANCE_BEAN_NAME_SET.add(r.getBeanName());continue;}}// 加入增强 Bean 集合NEED_ENHANCE_BEAN_NAME_SET.add(contextBeanName);} else if (ifNeedEnhanceBean.getNeedEnhanceEnum().equals(NeedEnhanceEnum.GLOBAL_TRANSACTIONAL_BEAN)) {// global transactional beanNEED_ENHANCE_BEAN_NAME_SET.add(contextBeanName);}} catch (ClassNotFoundException e) {LOGGER.warn("check if need enhance bean error, it can be ignore", e);}}LOGGER.info("The needed enhancement business beans are : {}", NEED_ENHANCE_BEAN_NAME_SET);}}
wrapIfNecessary() 是在 GlobalTransactionScanner 实现的 BeanPostProcessor 中被调用的,用于在 Spring 初始化 Bean 后判断是否需要增强成代理对象。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// do checkersif (!doCheckers(bean, beanName)) {return bean;}try {synchronized (PROXYED_SET) {if (PROXYED_SET.contains(beanName)) {return bean;}// 1. 不需要增强的 Bean 直接返回if (!NEED_ENHANCE_BEAN_NAME_SET.contains(beanName)) {return bean;}interceptor = null;//判断当前的Bean是否被增强ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(bean, beanName);if (proxyInvocationHandler == null) {return bean;}//创建拦截器interceptor = new AdapterSpringSeataInterceptor(proxyInvocationHandler);LOGGER.info("Bean [{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.toString());//没有被代理if (!AopUtils.isAopProxy(bean)) {bean = super.wrapIfNecessary(bean, beanName, cacheKey);} else {AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));int pos;for (Advisor avr : advisor) {// Find the position based on the advisor's order, and add to advisors by pospos = findAddSeataAdvisorPosition(advised, avr);advised.addAdvisor(pos, avr);}}PROXYED_SET.add(beanName);return bean;}} catch (Exception exx) {throw new RuntimeException(exx);}}
TM获取xid源码分析
GlobalTransactionalInterceptorHandler 是 Seata 中处理 @GlobalTransactional 注解事务增强逻辑的核心类之一,它负责在代理方法执行前后进行:
分布式事务的开启(Begin)
执行业务方法
异常捕获回滚(Rollback)
正常提交(Commit)
清理上下文
protected Object doInvoke(InvocationWrapper invocation) throws Throwable {//获取目标类Class<?> targetClass = invocation.getTarget().getClass();//获取代理的方法Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);//方法不为空if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {boolean localDisable = disable || (ATOMIC_DEGRADE_CHECK.get() && degradeNum >= degradeCheckAllowTimes);if (!localDisable) {//获取globalTransaction注解final AspectTransactional globalTransactionalAnnotation = getAspectTransactional(specificMethod, targetClass);//获取globalLock注解final GlobalLockConfig globalLockAnnotation = getGlobalLockConfig(specificMethod, targetClass);if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {AspectTransactional transactional;if (globalTransactionalAnnotation != null) {transactional = globalTransactionalAnnotation;} else {transactional = this.aspectTransactional;}//执行分布式事务return handleGlobalTransaction(invocation, transactional);} else if (globalLockAnnotation != null) {//执行分布式锁return handleGlobalLock(invocation, globalLockAnnotation);}}}return invocation.proceed();}
TransactionalTemplate 是对一个完整事务生命周期(开始、执行业务、提交或回滚)的封装:
public Object execute(TransactionalExecutor business) throws Throwable {// 1.获取事务信息TransactionInfo txInfo = business.getTransactionInfo();if (txInfo == null) {throw new ShouldNeverHappenException("transactionInfo does not exist");}// 获取当前的事务信息GlobalTransaction tx = GlobalTransactionContext.getCurrent();// 获取事务的传播机制Propagation propagation = txInfo.getPropagation();SuspendedResourcesHolder suspendedResourcesHolder = null;try {switch (propagation) {case NOT_SUPPORTED:// If transaction is existing, suspend it.if (existingTransaction(tx)) {suspendedResourcesHolder = tx.suspend(false);}// Execute without transaction and return.return business.execute();case REQUIRES_NEW:// If transaction is existing, suspend it, and then begin new transaction.if (existingTransaction(tx)) {suspendedResourcesHolder = tx.suspend(false);}tx = GlobalTransactionContext.createNew();// Continue and execute with new transactionbreak;case SUPPORTS:// If transaction is not existing, execute without transaction.if (notExistingTransaction(tx)) {return business.execute();}// Continue and execute with new transactionbreak;case REQUIRED:// If current transaction is existing, execute with current transaction,else createtx = GlobalTransactionContext.getCurrentOrCreate();break;case NEVER:// If transaction is existing, throw exception.if (existingTransaction(tx)) {throw new TransactionException(String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s", tx.getXid()));} else {// Execute without transaction and return.return business.execute();}case MANDATORY:// If transaction is not existing, throw exception.if (notExistingTransaction(tx)) {throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");}// Continue and execute with current transaction.break;default:throw new TransactionException("Not Supported Propagation:" + propagation);}// 设置当前事务的锁配置GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);//判断当前事务的角色if (tx.getGlobalTransactionRole() == GlobalTransactionRole.Participant) {LOGGER.info("join into a existing global transaction,xid={}", tx.getXid());}try {// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,//开启事务beginTransaction(txInfo, tx);Object rs;try {// 业务执行逻辑rs = business.execute();} catch (Throwable ex) {// 3. 回滚completeTransactionAfterThrowing(txInfo, tx, ex);throw ex;}// 4. 提交事务commitTransaction(tx, txInfo);return rs;} finally {//5. 清理resumeGlobalLockConfig(previousConfig);triggerAfterCompletion(tx);cleanUp(tx);}} finally {// If the transaction is suspended, resume it.if (suspendedResourcesHolder != null) {tx.resume(suspendedResourcesHolder);}}}
RM注册,生成undolog
SeataAutoDataSourceProxyCreator 是 Seata 的一个自动代理创建器组件,用于自动代理 Spring 容器中的 DataSource Bean,从而启用对数据库连接的分布式事务能力。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// we only care DataSource beanif (!(bean instanceof DataSource)) {return bean;}// when this bean is just a simple DataSource, not SeataDataSourceProxyif (!(bean instanceof SeataDataSourceProxy)) {Object enhancer = super.wrapIfNecessary(bean, beanName, cacheKey);// this mean this bean is either excluded by user or had been proxy beforeif (bean == enhancer) {return bean;}// else, build proxy, put <origin, proxy> to holder and return enhancerDataSource origin = (DataSource) bean;//组装我们的数据资源代理类SeataDataSourceProxy proxy = buildProxy(origin, dataSourceProxyMode);//加入到本地缓存DataSourceProxyHolder.put(origin, proxy);LOGGER.info("Auto proxy data source '{}' by '{}' mode.", beanName, dataSourceProxyMode);//返回return enhancer;}/** things get dangerous when you try to register SeataDataSourceProxy bean by yourself!* if you insist on doing so, you must make sure your method return type is DataSource,* because this processor will never return any subclass of SeataDataSourceProxy*/LOGGER.warn("Manually register SeataDataSourceProxy(or its subclass) bean is discouraged! bean name: {}", beanName);SeataDataSourceProxy proxy = (SeataDataSourceProxy) bean;DataSource origin = proxy.getTargetDataSource();Object originEnhancer = super.wrapIfNecessary(origin, beanName, cacheKey);// this mean origin is either excluded by user or had been proxy beforeif (origin == originEnhancer) {return origin;}// else, put <origin, proxy> to holder and return originEnhancerDataSourceProxyHolder.put(origin, proxy);return originEnhancer;}
组装SeataDataSourceProxy代理类
SeataDataSourceProxy buildProxy(DataSource origin, String proxyMode) {//事务模式ATif (BranchType.AT.name().equalsIgnoreCase(proxyMode)) {return new DataSourceProxy(origin);}//事务模式XAif (BranchType.XA.name().equalsIgnoreCase(proxyMode)) {return new DataSourceProxyXA(origin);}throw new IllegalArgumentException("Unknown dataSourceProxyMode: " + proxyMode);}
初始化DataSourceProxy类
public DataSourceProxy(DataSource targetDataSource, String resourceGroupId) {if (targetDataSource instanceof SeataDataSourceProxy) {LOGGER.info("Unwrap the target data source, because the type is: {}", targetDataSource.getClass().getName());targetDataSource = ((SeataDataSourceProxy) targetDataSource).getTargetDataSource();}this.targetDataSource = targetDataSource;init(targetDataSource, resourceGroupId);}private void init(DataSource dataSource, String resourceGroupId) {this.resourceGroupId = resourceGroupId;//这个地方是核心,通过获取代理连接ConnectionProxy类try (Connection connection = dataSource.getConnection()) {jdbcUrl = connection.getMetaData().getURL();dbType = JdbcUtils.getDbType(jdbcUrl);if (JdbcConstants.ORACLE.equals(dbType)) {userName = connection.getMetaData().getUserName();} else if (JdbcConstants.MYSQL.equals(dbType)) {validMySQLVersion(connection);checkDerivativeProduct();}checkUndoLogTableExist(connection);} catch (SQLException e) {throw new IllegalStateException("can not init dataSource", e);}if (JdbcConstants.SQLSERVER.equals(dbType)) {LOGGER.info("SQLServer support in AT mode is currently an experimental function, " +"if you have any problems in use, please feedback to us");}//初始化resourceIdinitResourceId();//注册当前资源DefaultResourceManager.get().registerResource(this);TableMetaCacheFactory.registerTableMeta(this);//Set the default branch type to 'AT' in the RootContext.RootContext.setDefaultBranchType(this.getBranchType());}
ConnectionProxy 这个连接类是我们插入undolog以及注册RM的核心类,在业务逻辑操作数据库时,我们需要将当前的Rm注册。
//数据库提交时,我们将当前的RM以及undolog插入操作完成public void commit() throws SQLException {try {lockRetryPolicy.execute(() -> {doCommit();return null;});} catch (SQLException e) {if (targetConnection != null && !getAutoCommit() && !getContext().isAutoCommitChanged()) {rollback();}throw e;} catch (Exception e) {throw new SQLException(e);}}
private void doCommit() throws SQLException {//判断是否是全局事务if (context.inGlobalTransaction()) {processGlobalTransactionCommit();} else if (context.isGlobalLockRequire()) {//判断如果是全局锁processLocalCommitWithGlobalLocks();} else {//提交当前的事务targetConnection.commit();}}
只要我们的业务使用了seata的DataSourceProxy,只要有数据库的操作,我们就会生成undolog和注册分支。
private void processGlobalTransactionCommit() throws SQLException {try {//RM注册register();} catch (TransactionException e) {recognizeLockKeyConflictException(e, context.buildLockKeys());}try {//插入undologUndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this);//当前的数据库事务执行提交targetConnection.commit();} catch (Throwable ex) {LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex);report(false);throw new SQLException(ex);}if (IS_REPORT_SUCCESS_ENABLE) {report(true);}context.reset();}
TC的启动和接收请求流程
TC的业务功能:
协调者(Coordinator) 管理全局事务的开始、提交、回滚等控制流
状态管理者(Session Manager) 维护所有全局和分支事务的状态
重试调度者(Retry Manager) 对失败的提交/回滚事务做重试处理
资源通知者(RM 通信) 向参与者 RM(Resource Manager)发送分支注册、提交、回滚命令
事务持久化者 将事务信息持久化到数据库或文件系统(如 file/db/redis 模式)
ServerRunner在seataServer启动时,就是执行run()方法:
public void run(String... args) {try {long start = System.currentTimeMillis();seataServer.start(args);started = true;long cost = System.currentTimeMillis() - start;LOGGER.info("\r\n you can visit seata console UI on http://127.0.0.1:{}. \r\n log path: {}.", this.port, this.logPath);LOGGER.info("seata server started in {} millSeconds", cost);} catch (Throwable e) {started = Boolean.FALSE;LOGGER.error("seata server start error: {} ", e.getMessage(), e);System.exit(-1);}}//seataServer启动的方法public void start(String[] args) {//劫器启动参数ParameterParser parameterParser = new ParameterParser(args);//设置网络信息MetricsManager.get().init();//初始化线程池ThreadPoolExecutor workingThreads = new ThreadPoolExecutor(NettyServerConfig.getMinServerPoolSize(),NettyServerConfig.getMaxServerPoolSize(), NettyServerConfig.getKeepAliveTime(), TimeUnit.SECONDS,new LinkedBlockingQueue<>(NettyServerConfig.getMaxTaskQueueSize()),new NamedThreadFactory("ServerHandlerThread", NettyServerConfig.getMaxServerPoolSize()), new ThreadPoolExecutor.CallerRunsPolicy());//127.0.0.1 and 0.0.0.0 are not valid here.if (NetUtil.isValidIp(parameterParser.getHost(), false)) {XID.setIpAddress(parameterParser.getHost());} else {String preferredNetworks = ConfigurationFactory.getInstance().getConfig(REGISTRY_PREFERED_NETWORKS);if (StringUtils.isNotBlank(preferredNetworks)) {XID.setIpAddress(NetUtil.getLocalIp(preferredNetworks.split(REGEX_SPLIT_CHAR)));} else {XID.setIpAddress(NetUtil.getLocalIp());}}//启动 Netty 服务端NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads);XID.setPort(nettyRemotingServer.getListenPort());UUIDGenerator.init(parameterParser.getServerNode());ConfigurableListableBeanFactory beanFactory =((GenericWebApplicationContext) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)).getBeanFactory();//初始化事务协调器 DefaultCoordinatorDefaultCoordinator coordinator = DefaultCoordinator.getInstance(nettyRemotingServer);if (coordinator instanceof ApplicationListener) {beanFactory.registerSingleton(NettyRemotingServer.class.getName(), nettyRemotingServer);beanFactory.registerSingleton(DefaultCoordinator.class.getName(), coordinator);((GenericWebApplicationContext) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)).addApplicationListener((ApplicationListener<?>) coordinator);}//初始化会话存储、分布式锁等核心组件SessionHolder.init();LockerManagerFactory.init();coordinator.init();nettyRemotingServer.setHandler(coordinator);serverInstance.serverInstanceInit();ServerRunner.addDisposable(coordinator);nettyRemotingServer.init();
}
coordinator.init()方法
public void init() {//回滚retryRollbacking.scheduleAtFixedRate(() -> SessionHolder.distributedLockAndExecute(RETRY_ROLLBACKING, this::handleRetryRollbacking), 0,ROLLBACKING_RETRY_PERIOD, TimeUnit.MILLISECONDS);//提交重试retryCommitting.scheduleAtFixedRate(() -> SessionHolder.distributedLockAndExecute(RETRY_COMMITTING, this::handleRetryCommitting), 0,COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);//异步提交asyncCommitting.scheduleAtFixedRate(() -> SessionHolder.distributedLockAndExecute(ASYNC_COMMITTING, this::handleAsyncCommitting), 0,ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);//超时操作timeoutCheck.scheduleAtFixedRate(() -> SessionHolder.distributedLockAndExecute(TX_TIMEOUT_CHECK, this::timeoutCheck), 0,TIMEOUT_RETRY_PERIOD, TimeUnit.MILLISECONDS);//undolog删除操作undoLogDelete.scheduleAtFixedRate(() -> SessionHolder.distributedLockAndExecute(UNDOLOG_DELETE, this::undoLogDelete),UNDO_LOG_DELAY_DELETE_PERIOD, UNDO_LOG_DELETE_PERIOD, TimeUnit.MILLISECONDS);rollbackingSchedule(0);committingSchedule(0);}
Seata常见的异常场景
一、事务执行阶段异常
-
业务执行中抛异常(业务代码中 throw)
表现:业务代码抛出异常,比如空指针、断言失败、自定义异常等;
后果:Seata 会自动触发 rollback;
风险:需确保触发异常前数据库操作没有提前 commit。 -
业务成功但未抛异常,Seata 没有拦截到异常
原因:开发者错误处理了异常(try-catch 吞了异常);
后果:Seata 认为事务成功,可能导致脏数据;
建议:不要吞掉业务异常,应抛出让 Seata 能够感知。
二、分支事务异常
- 分支事务注册失败
原因:RM 在 branchRegister 时 TC 无响应或 TC 异常;
后果:全局事务无法提交,业务事务可能提交但未被管控;
建议:增加分支注册超时处理、监控 TC 状态。 - 分支事务提交失败
原因:RM commit 过程中数据库异常、连接池问题;
后果:TC 会将该分支转入 Async Committing 队列重试;
机制:Seata 有异步提交补偿机制。 - 分支事务回滚失败
原因:undo_log 被删;数据库回滚时违反约束;数据被其他线程/请求修改;
后果:进入 Retry Rollbacking 队列重试;
建议:保留 undo_log;避免并发修改数据;确保回滚 SQL 可用。
三、全局事务异常
- TM 与 TC 网络断连
表现:begin, commit, rollback 时超时;
后果:事务状态不一致,需手动干预;
建议:启用重试机制或熔断报警。 - 全局提交失败
原因:RM 不可达;数据库异常;
后果:进入 Async Committing 队列;分支可能未一致提交;
机制:TC 有定时重试机制。 - 全局回滚失败
后果:回滚不彻底;
有部分分支成功、有部分失败;
机制:进入 Retry Rollbacking,直到成功或人工介入。
四、UndoLog 异常相关
- 找不到 undo_log
原因:被误删;数据库未开启事务;
后果:无法回滚,分支事务失败;
建议:保证 undo_log 插入成功、不可被清理。 - undo_log 解析失败
原因:Seata 升级引起结构不兼容;
自定义类型或 BLOB 反序列化失败;
建议:注意 Seata 版本兼容性、避免复杂字段写入。
五、数据库与资源问题
- 数据库连接池耗尽
场景:高并发事务下未及时释放连接;
建议:设置合理连接池参数,并释放资源及时。 - 锁冲突 / 死锁
场景:多个全局事务互相更新相同资源;
建议:使用乐观锁,避免热点数据事务。