☰
订单与库存的分布式事务:@Transactional为何无效及正确解法
2026/10/7 14:35:52 网站建设 项目流程

面试官问:“订单和库存是两个服务,怎么保证数据一致?”如果你脱口而出“加个@Transactional”,我建议你先把这篇文章看完。

分布式事务这道题,面试官真正想听的从来不是一个注解,而是你对事务边界的理解:哪些操作能放在同一个本地事务里,哪些只能靠最终一致性兜底。很多人把@Transactional当成“万能事务开关”,在跨服务调用、多数据源、发消息的场景里随手一加,线上数据不对了,第一反应还是“是不是我事务粒度不对”。问题不在粒度,而在方向。

一句话给出本文判断:@Transactional只能管理本地数据库事务,它管不了跨服务、跨库、跨消息的一致性。真正能解决“订单和库存分布式事务”的,是本地消息表、消息事务、TCC、SAGA这类分布式事务方案。本文会把原理、典型错误场景、订单库存实战代码、面试答题框架和生产排错清单一次讲清楚。

1. 先看结论:为什么@Transactional不是分布式事务的答案

先明确一个容易混淆的概念:什么才算分布式事务。

一个Service方法里写了三个Mapper,虽然代码里有“多张表、多个操作”,但它们最终都指向同一个数据库,处于同一个数据库连接中。这不是分布式事务,这是典型的本地事务。@Transactional可以管理它,问题是“本地事务能不能被管住”,而不是“分布式事务能不能被管住”。

真正的分布式事务,至少要满足下面之一:

  • 数据分散在多个数据库实例;
  • 操作跨越多个服务进程;
  • 一个业务流程既写数据库,又发MQ消息;
  • 一个业务流程调用第三方HTTP/RPC接口,并且需要保证多方数据一致。

订单场景就是典型:订单数据在订单库,库存数据在库存库,中间还要经过网络调用。两个库各有一个独立事务,@Transactional在订单服务里能管的只有订单库。库存服务提交或回滚,和订单服务本地事务是两个完全独立的过程。

所以从结论上看,分布式事务问题的本质是:多个独立资源之间的一致性,无法靠单个数据库的事务管理器去协调。你越早接受“@Transactional不是分布式事务答案”这个事实,越能在面试和线上问题排查里少走弯路。

那@Transactional到底能做什么?下一节把它讲透。

2. @Transactional的原理与边界

2.1 它是怎么生效的

Spring的@Transactional本质是AOP代理。方法进入之前,代理通过PlatformTransactionManager开启一个数据库事务;方法正常退出,代理提交事务;方法抛出异常,代理回滚事务。

具体到最常用的DataSourceTransactionManager,它的逻辑是把一个数据库连接绑定到当前线程。同一个线程内后续执行的SQL,都拿的是这个连接。所以本地事务的边界,其实是一个数据库连接。多个SQL要么一起提交,要么一起回滚。

示例代码(本地事务的正确用法):

// 文件路径:src/main/java/com/example/order/service/OrderService.java @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderItemMapper orderItemMapper; @Transactional(rollbackFor = Exception.class) public void createLocalOrder(OrderDO order, List<OrderItemDO> items) { // 订单表和订单明细表在同一数据库 orderMapper.insert(order); for (OrderItemDO item : items) { orderItemMapper.insert(item); } } }

这段代码里,订单表和明细表的写入要么全部成功,要么全部回滚。为什么能做到?因为两个Mapper用的是同一个DataSource,同一个数据库连接,处于同一个本地事务里。

2.2 它管不住什么

用同样的方式扣库存,就不行了:

// 文件路径:src/main/java/com/example/order/service/OrderService.java @Transactional(rollbackFor = Exception.class) public void createOrderWithRemote(OrderDO order) { // 本地库插入订单 orderMapper.insert(order); try { // 远程调用库存服务,这里是另一个数据库连接和另一个事务 inventoryService.deductStock(order.getSkuId(), order.getCount()); } catch (Exception e) { log.warn("扣库存失败,但订单本地事务仍然可能提交", e); } }

这段代码有几个致命问题:

  1. inventoryService.deductStock是远程调用,它走的是另外一条链路,库存服务有自己的事务管理器,和当前方法的事务管理器不是同一个。
  2. 即使把异常catch住再抛出,Spring能回滚的也只是订单库本地事务,库存服务已经扣减的库存不会自动回滚。
  3. 如果catch住异常不放出去,代理根本不知道发生了异常,本地事务照样提交。订单提交了,库存没扣,数据不一致。

这里真正容易踩坑的是第三点:很多人以为“只要远程调用的异常能传回调用方,事务就会回滚”。实际上,无论异常有没有传回,跨服务的一致性都不会因为一个本地事务注解而解决。最坏的情况是:远程库存扣减成功,本地订单插入失败回滚,导致库存被白白扣了。

2.3 一个容易被追问的失效场景

@Transactional还有一个经典失效点:同一个类内部调用。

@Service public class OrderService { @Transactional public void outerMethod() { // 这里走的不是代理,是this对象 this.innerInsert(); throw new RuntimeException("触发回滚"); } public void innerInsert() { orderMapper.insert(new OrderDO()); } }

Spring的AOP代理只对从外部进入的调用生效。outerMethod内部调用innerInsert时,调用的是当前对象的方法,不会重新走代理,所以@Transactional不会生效。面试里经常把这个包装成“自调用导致事务失效”,本质上还是没理解“代理”这两个字。

3. 跨服务场景为什么管不住:从CAP到最终一致性

3.1 网络是不可靠的

跨服务的第一道坎是网络。远程调用有可能成功,有可能失败,有可能“实际上成功但响应超时”。

订单服务调用库存服务扣库存,如果响应超时,订单服务无法确定库存到底扣没扣。此时无论订单服务怎么回滚,都不一定能把库存状态改回去。这个不确定性是本地事务不存在的:数据库连接上的SQL成功与否,数据库自己知道;但网络另一端的服务,调用方无法直接感知。

3.2 CAP和分布式事务的代价

分布式系统绕不开CAP:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。网络分区必然存在,所以CP和AP之间必须做取舍。

  • 选择强一致性,往往要牺牲可用性。XA两阶段提交就是这种模式:所有参与者先prepare,全部成功后coordinator再发commit,期间锁资源、阻塞事务。
  • 选择高可用,就只能接受最终一致性。先把请求落库,异步通知下游,下游成功了整个流程成功,失败了通过重试和补偿慢慢纠正。

很多业务的正确姿势是后者:允许中间状态存在,但最终状态是对的。比如订单先创建,状态“待扣库存”,再过几百毫秒库存扣减成功,订单状态变成“已下单”。用户感受到的是下单成功,不需要感知中间过程。

3.3 一致性级别

再补充一个面试高频概念:分布式一致性不是只有“有”和“没有”两级,而是有强弱之分。

  • 强一致性:数据一旦写入,任何线程、任何服务立刻能读到最新值。XA可以近似做到,但成本高。
  • 弱一致性:写入后不保证立刻读到,只承诺后期会一致。
  • 最终一致性:弱一致性的一种,系统保证“如果没有新的更新,经过一段时间后数据会一致”。这是大多数异步消息方案的目标。

订单和库存的选择,业务上通常允许“先下单,后扣库存”,所以最终一致性就够了。这也是为什么面试官听你说出“最终一致性”之后,会接着追问:那你要怎么保证最终一致?

4. 四个典型的错误姿势:面试和生产里的高频翻车点

4.1 错误一:@Transactional + RPC调用

这是最常见的错误。前面代码里已经演示过。核心问题是:把远程调用的不确定性包装进本地事务,产生“假事务”。

更合理的做法是:本地事务只负责写库,写完之后通过消息或定时任务异步通知下游,而不是在事务里面直接等远程结果。

4.2 错误二:@Transactional + 多数据源

有些同学觉得“我配置了多个DataSource,@Transactional加上去,两个库就是同一个事务了”。这是对Spring事务管理器的误解。

Spring默认的事务管理器一次只绑定一个DataSource。如果Service方法里操作了DataSourceA和DataSourceB,@Transactional只能管到由事务管理器指定的那一个数据源。另一个数据源的操作要么在自己的连接里自动提交,要么根本不参与事务。

要让多个库参与同一个事务,需要引入JTA(Java Transaction API)或Atomikos之类的分布式事务管理器。但这会进入XA两阶段提交的范畴,性能和可用性都有代价,不能当成默认选择。

4.3 错误三:@Transactional 里发送MQ消息

下面这段代码看起来“挺对”,实际有坑:

@Transactional public void createOrder(OrderDO order) { orderMapper.insert(order); // 事务还没提交,消息就先发出去了 mqProducer.send("order-topic", order); }

如果消息先发,数据库事务后提交,消费者可能在“订单还没真正落库”的时候就收到了消息,去查订单却发现查不到。反过来,如果事务提交成功但消息发送失败,消息就丢了。这正是“本地事务和消息发送原子性”的问题,需要专门方案解决:要么本地消息表,要么消息事务。

4.4 错误四:自调用导致事务失效

如前所述,这是很隐蔽的坑。面试官问“事务不生效的原因有哪些”,至少应该能说出:方法要public、异常要被Spring代理捕获且符合rollbackFor条件、同类内部调用不经过代理、数据库引擎要支持事务(比如MySQL要InnoDB,MyISAM就不支持)。这些都是排查“事务不生效”的标准思路。

5. 分布式事务方案盘点:XA、TCC、SAGA与消息方案

面试要答好分布式事务,不是把方案名背一遍,而是要能说清楚每个方案的取舍。下面用一个表把主流方案讲明白:

方案核心思路一致性优点缺点适用场景
XA两阶段提交资源管理器统一协调,prepare后commit强一致数据一致性最强阻塞、性能差、协调者单点,维护成本高交易系统内部低并发强一致场景,跨库跨服务使用较少
TCC(Try/Confirm/Cancel)业务层分阶段操作,预留资源,确认提交,失败补偿业务层可控的强/最终一致灵活性高,对业务隔离好实现复杂,每个业务都要写Try/Confirm/Cancel三段逻辑需要明确预留资源的场景,比如账户冻结、扣款
SAGA把一个长事务拆成多个本地事务,每个事务配一个补偿操作最终一致适合长流程,不需要长时间锁资源中间状态可见,补偿逻辑复杂,可能出现部分成功订单流程、旅游预订、多步骤业务流程
本地消息表业务数据和消息数据在同一本地事务中写入,异步发送最终一致实现简单,不依赖特殊中间件需要额外开发定时任务和消息表,存在重复发送绝大多数订单、库存场景
消息事务MQ支持“先半消息,后提交/回滚”,本地事务完成后决定消息投递最终一致减少本地消息表开发,MQ负责回查依赖支持事务消息的MQ(如RocketMQ),运维成本增加有RocketMQ的团队常见选择

我的判断是:真实业务中,60%到70%的跨服务一致性场景其实可以用“本地消息表 + 幂等消费 + 定时对账”解决。TCC和SAGA不是不好,而是复杂度高,设计和使用成本都大,通常只在强业务规则、高资金风险、需要资源预占的场景里才值得上。

面试时不要一上来就抛TCC。先把“最终一致性 + 消息 + 幂等”讲清楚,再补充TCC、SAGA在什么情况下才会考虑,显得你是一个有业务判断力的候选人,而不是只会背概念。

6. 订单与库存场景的正确解法

回到面试和实战都最高频的问题:用户下单,订单服务创建订单,库存服务扣库存。怎么保证两个服务的数据最终一致?

6.1 方案一:本地消息表

本地消息表的核心思想是:把“业务操作”和“待发送消息”放在同一个本地数据库事务里。事务提交之后,由异步任务把消息发出去。这样只要订单落库成功,消息记录就一定存在;消息发送失败也没关系,定时任务不断重试。

流程如下:

  1. 订单服务本地事务:插入订单记录 + 插入一条“待发送消息”记录。
  2. 事务提交。
  3. 定时任务扫描“待发送消息”,把消息发送到MQ或直接调用库存服务接口。
  4. 库存服务消费消息,扣减库存。
  5. 库存服务需要使用唯一ID做幂等,防止重复扣库存。

先看核心代码:

// 文件路径:src/main/java/com/example/order/service/OrderService.java @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private LocalMessageMapper localMessageMapper; @Transactional(rollbackFor = Exception.class) public void createOrderWithMessage(OrderDO order) { // 第一步:本地事务里写业务数据 orderMapper.insert(order); // 第二步:同时写入本地消息表 LocalMessageDO message = new LocalMessageDO(); message.setMessageId(UUID.randomUUID().toString()); message.setOrderId(order.getOrderId()); message.setContent(JsonUtils.toJson(order)); message.setStatus(0); // 0=待发送,1=已发送 localMessageMapper.insert(message); } }

订单和本地消息表在同一个库、同一个事务里。只要createOrderWithMessage方法正常返回,消息表里一定有一条待发送记录;如果订单插入失败,消息也不会存在。

接着是异步发送任务:

// 文件路径:src/main/java/com/example/order/job/MessageSendJob.java @Component public class MessageSendJob { @Autowired private LocalMessageMapper localMessageMapper; @Autowired private InventoryServiceClient inventoryServiceClient; @Scheduled(fixedDelay = 5000) public void sendPendingMessage() { List<LocalMessageDO> pending = localMessageMapper.selectByStatus(0); for (LocalMessageDO message : pending) { try { inventoryServiceClient.deductStock(message.getContent()); localMessageMapper.updateStatus(message.getId(), 1); } catch (Exception e) { // 记录日志,消息保持待发送状态,下次继续尝试 } } } }

这里有一个关键点:消息发送成功后,要把消息状态改成“已发送”。如果发送成功但更新状态失败,这个任务会重复发送同一条消息。所以库存服务的消费端必须幂等。

库存服务侧的核心代码:

// 文件路径:src/main/java/com/example/inventory/consumer/InventoryConsumer.java @Service public class InventoryConsumer { @Autowired private InventoryMapper inventoryMapper; @Autowired private ConsumeLogMapper consumeLogMapper; @Transactional(rollbackFor = Exception.class) public void deductStock(String messageId, Long skuId, Integer count) { // 用消费日志表做幂等,messageId有唯一约束 int inserted = consumeLogMapper.tryInsert(messageId); if (inserted == 0) { // 已处理过,直接返回 return; } // 扣减库存,SQL里用条件防止超卖 int affected = inventoryMapper.decreaseWithCondition(skuId, count); if (affected == 0) { throw new RuntimeException("库存不足"); } } }

幂等逻辑很关键。如果库存服务收到两次相同消息,第一次处理成功,第二次会被consumeLogMapper.tryInsert挡住,不再执行扣减。如果去掉幂等,定时任务重试一次,库存就被多扣了一次。

6.2 方案二:RocketMQ事务消息

如果团队已经使用支持事务消息的MQ(典型是RocketMQ),可以用事务消息替代本地消息表。核心区别在于:本地消息表把“消息”放在业务库中,事务消息则交给MQ来保证“业务事务”和“消息发送”的原子性。

RocketMQ事务消息的流程是:

  1. 订单服务发送一条“半消息”,Broker暂时不投递给消费者。
  2. 订单服务执行本地事务(创建订单)。
  3. 本地事务执行成功,返回COMMIT_MESSAGE;执行失败,返回ROLLBACK_MESSAGE。
  4. Broker收到COMMIT后,才把消息投递给库存服务。
  5. 如果订单服务在步骤3宕机,Broker会通过回查机制询问本地事务结果,再决定提交或回滚。

代码示例:

// 文件路径:src/main/java/com/example/order/mq/OrderTransactionListener.java @Component public class OrderTransactionListener implements TransactionListener { @Autowired private OrderService orderService; @Override public LocalTransactionState executeLocalTransaction(Message message, Object arg) { try { OrderDO order = (OrderDO) arg; orderService.createOrder(order); return LocalTransactionState.COMMIT_MESSAGE; } catch (Exception e) { return LocalTransactionState.ROLLBACK_MESSAGE; } } @Override public LocalTransactionState checkLocalTransaction(MessageExt message) { // 回查:订单是否存在 String orderId = message.getUserProperty("orderId"); boolean exists = orderService.isOrderExists(orderId); return exists ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE; } }

发送半消息的入口大致如下:

// 文件路径:src/main/java/com/example/order/service/OrderProducer.java @Service public class OrderProducer { @Autowired private TransactionMQProducer transactionMQProducer; public void sendOrderMessage(OrderDO order) { try { Message message = new Message(); message.setTopic("order-topic"); message.setBody(JsonUtils.toJson(order).getBytes(StandardCharsets.UTF_8)); message.putUserProperty("orderId", order.getOrderId()); // 发送半消息,并传入本地事务参数 transactionMQProducer.sendMessageInTransaction(message, order); } catch (Exception e) { // 这里需要处理发送失败的情况 throw new RuntimeException("发送事务消息失败", e); } } }

库存服务消费侧,同样要遵循“先幂等,再扣库存”的原则。事务消息能解决的只是生产者侧的原子性,消费者侧仍然是“至少一次投递”,重复消费只能靠应用层幂等兜住。

6.3 两个方案怎么选

这个问题的判断标准不复杂:

  • 如果项目里已经有RocketMQ,并且团队能接受RocketMQ的运维成本,事务消息更优雅,省掉了本地消息表和定时任务。
  • 如果团队只有Kafka或RabbitMQ,且不想为事务消息引入新组件,本地消息表是最稳妥的通用方案。
  • 如果项目并发不高、团队规模小,本地消息表优先,因为逻辑透明,排查方便。

无论选哪个,都要记得:分布式事务没有银弹,核心是“消息可靠 + 消费幂等 + 定时对账”。这三件事做到位,大多数订单库存问题都能兜住。

7. 面试避坑:答题框架与常见追问

7.1 一个稳健的回答框架

面试官问“订单和库存的分布式事务怎么解决”,可以按下面步骤回答:

第一步:明确事务边界。先说“订单和库存是两个服务、两个数据库,无法用单个数据库的本地事务解决,@Transactional只能管订单库本地操作”。

第二步:做一致性取舍。再说“订单库存场景可以接受最终一致性,先创建订单,再异步扣库存”。

第三步:给具体方案。可以选择本地消息表或事务消息。要讲清楚:业务表和消息表同库同事务,异步发送,下游幂等消费。

第四步:补充兜底机制。说明“消息发送失败,由定时任务重试;消费重复,靠唯一ID防重;还有对账任务兜底”。

这套回答的关键是:先边界,再取舍,再落地,最后补兜底。面试官听到的是“这个候选人真的处理过问题”,而不是“背了一堆方案”。

7.2 常见追问有哪些

  • “消息丢了怎么办?”

    答:如果使用本地消息表,业务表和消息表在同一事务里,消息不会凭空消失;发送失败由定时任务重试。如果使用RocketMQ事务消息,半消息是否提交取决于本地事务结果,还有回查机制兜底。另外,业务侧还要做定时对账,比如每天扫描没有扣库存的订单,主动补发。

  • “消费者重复消费了怎么办?”

    答:消息中间件一般只保证至少一次投递,所以消费端必须幂等。常见做法:消费记录表加唯一约束,或者用数据库唯一索引、Redis setnx做防重,扣减库存使用条件更新避免超卖。

  • “库存服务处理成功后,回执丢了怎么办?”

    答:接受“最终一致”的设计前提。只要消费者端幂等,消息重发也能保证不重复扣减库存。处理成功的回执丢失不改变数据结果,重试一次还会读到消费记录。

  • “你说用TCC,那TCC的Confirm阶段失败了怎么办?”

    答:TCC也不能保证绝对不失败,Confirm失败后通常需要重试、幂等,再不行走人工或对账补偿。TCC的价值在于Try阶段预留资源,把失败可能性降低,但不可能消除。

  • “你们生产上真有分布式事务吗?”

    答:这里务必诚实。如果没做过,就说“我学习过,也熟悉方案,但目前生产场景主要用X方案”。不要编造经历。面试官更看重思路,不靠编故事。

7.3 面试中不要说的话

  • “我们直接加个分布式事务中间件就行”——没有结合业务取舍。
  • “@Transactional也能解决跨库”——说明没理解事务边界。
  • “强一致和最终一致没区别”——说明没理解分布式系统的本质。
  • “Kafka能发事务消息就能解决所有问题”——事务消息只解决生产者侧,消费幂等还是要自己处理。

8. 常见问题与排查方法

把线上常见的分布式事务问题整理成一张排查表,建议收藏。

问题现象可能原因排查方式解决方案
订单已创建,库存没扣本地消息表发送任务失败,或消费失败重试用尽查本地消息表status字段、发送任务日志、MQ消费位点增强定时重发,增加消费失败重试次数,超限进入死信或告警
库存扣了,但订单不存在消息在本地事务提交前被消费者读取,或订单事务回滚但消息已发看消息消费时间和订单落库时间,检查发送是否在事务提交后使用afterCommit发送,或直接使用本地消息表/事务消息
@Transactional内远程调用失败,本地事务仍然提交远程调用异常被捕获,代理无法感知在方法入口和异常捕获处打印日志,确认是否有异常抛出不把远程调用放在本地事务中,改用消息异步通知
多数据源场景一个库回滚另一个库提交没有配置全局事务管理器,@Transactional只绑定一个数据源查看两个库的binlog或业务日志,确认提交时间点引入JTA/XA,或调整设计为最终一致性方案
库存被重复扣减消费者重复消费,没有幂等处理查消费记录表中是否存在相同messageId增加唯一约束,使用幂等判断后再扣库存
定时任务重复发送同一条消息消息表没有唯一约束,多个任务实例并发扫描查消息表是否有重复记录,检查任务是否加了分布式锁消息表加唯一索引,任务调度加分布式锁
订单状态长时间停留在“待扣库存”对账任务没有触发,或补发机制缺失查对账任务日志、消息表、订单状态流转记录增加定时对账任务,对超时订单主动补发或人工处理

排查分布式事务问题的核心思路,不是去猜哪一行代码写错了,而是先看“数据流的每一步终态”。订单状态是什么,消息表状态是什么,消费记录是否存在,库存流水是什么。沿着这四个点看,问题范围很快能缩小。

9. 最佳实践与工程建议

9.1 明确事务边界,别把外部IO放进来

一个方法里如果同时有数据库操作和远程调用,优先考虑拆分:

  • 数据库操作放在本地事务中;
  • 远程调用放在事务外;
  • 需要保证两者一致时,用消息或事件驱动,而不是在事务里直接等待跨服务结果。

如果确实要在事务提交后做点事,可以注册afterCommit回调:

@Transactional public void createOrder(OrderDO order) { orderMapper.insert(order); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { mqProducer.send("order-topic", order); } }); }

这样可以保证“事务提交成功后再发消息”,比直接在事务里发消息安全。但要清楚,它仍然不能解决“消息发送失败后的重试”问题,真正的可靠性还要靠本地消息表或事务消息。

9.2 幂等是分布式事务的地基

无论选哪种方案,幂等都是底线。建议在消息表、消费记录表、流水表等关键数据表上加唯一约束。唯一约束比“先查再判”更可靠,因为并发场景下“查不到然后插入”可能因为并发产生重复。

扣减库存建议用条件更新,避免并发超卖:

update inventory set stock = stock - #{count} where sku_id = #{skuId} and stock >= #{count}

如果影响行数为0,说明库存不足,业务上再做回滚或提示。

9.3 消息可靠性要分层

“消息不会丢”不是单一环节能保证的,而是整条链路共同保证:

  • 发送端:业务数据与消息数据同事务,或者使用事务消息;
  • 传输端:MQ本身要做持久化;
  • 消费端:先幂等,再处理业务;
  • 兜底层:定时对账任务,发现不一致,主动补齐。

9.4 尽量从架构上减少分布式事务

一个经常被忽略的最佳实践是:能不跨服务就不跨服务。订单和库存如果必须强一致,可以考虑把订单和库存的写入放到同一个服务、同一个数据库内,用本地事务管理。只有当业务确实无法聚合时,才引入分布式事务方案。

很多公司为了微服务而微服务,把一个强一致的写操作拆成两个服务,然后花大力气解决分布式事务,这是本末倒置。架构设计阶段先问一句:这两个数据真的必须分开吗?

9.5 对账和人工补偿是最后防线

再好的系统也会有极端异常。建议保留订单状态流转日志、库存流水、消息记录,方便对账。对账任务可以用定时Job,每天扫描“已创建但未扣库存”的订单,超过一定时间就告警或自动补发。

线上分布式事务出问题时,第一原则是:先止血,保留现场,再复盘。不要为了“尽快恢复”而乱改数据,导致问题无法追溯。

9.6 面试准备建议

如果你正在准备面试,不要只背TCC的定义。做一个练习题:把你负责过的业务,画出完整调用链,标出哪些是本地事务,哪些是跨服务,失败时数据会发生什么状态,然后设计一套补偿方案。

这个功夫花下去,比背十个方案都有用。面试官问“分布式事务”,本质上是想确认你是不是一个能在真实复杂环境下保证数据不出问题的工程师。你能把自己的业务讲清楚,比任何概念都更有说服力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询