分布式事务面试题:XA/JTA、TCC、Saga、Outbox与Seata
本页只放标准回答和精确原理入口。完整状态机、SQL、Demo与Runbook从事务总览进入各方案页。
总览与选型
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| 分布式事务是什么 | 当业务不变量跨多个独立提交边界时,在超时、宕机、重复和部分成功下协调提交、补偿或最终恢复的一类方案,不等于Seata。 | 问题定义、本地/分支/全局事务 |
| 为什么Spring本地事务管不了远程服务 | 本地事务绑定当前事务资源/连接,远程服务使用独立进程和数据库;当前事务回滚不会自动撤销已提交的远端事务。 | 事务边界 |
| 分布式事务怎样选 | 先定义不变量、时长、可逆性、吞吐、资源能力和隔离;短强一致考虑XA,预留资源考虑TCC,长流程考虑Saga,后置同步考虑Outbox/事务消息。 | 选型维度、矩阵 |
| 为什么任何方案都需要幂等 | 协调命令、消息和补偿会因超时重发,参与者必须让重复Prepare/Confirm/Cancel/消费不产生重复副作用。 | 失败窗口、恢复设计 |
XA、JTA与2PC
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| JTA、XA、2PC是什么关系 | JTA是Java事务API,XA定义TM与资源之间的接口和Xid,2PC是Prepare后统一Commit/Rollback的协议过程;TM负责登记、日志和恢复。 | 概念地图、资源登记 |
| 2PC完整流程 | TM让各分支执行并End,第一阶段Prepare收集投票并持久化;全部成功形成Commit决议,否则Rollback;第二阶段重复下发最终决议直到收敛。 | 两阶段流程、投票 |
| in-doubt是什么 | 分支已Prepare并持久化恢复状态,但联系不到TM,不知道最终决议,通常保留锁/上下文等待恢复,不能自行随意提交。 | in-doubt |
| TM重启怎样恢复 | 读取协调日志,重新获得XAResource,调用recover扫描Prepared Xid,与日志决议对齐后重发Commit或Rollback。 | TM日志、恢复扫描 |
| 启发式结果是什么 | 资源自行作出与全局决议不同的提交/回滚,产生Heuristic Commit/Rollback/Mixed/Hazard,原子性已破坏,必须逐资源对账。 | 启发式结果 |
| XA为什么成本高 | Prepare需要日志和可恢复状态,可能长期持锁,增加网络轮次、协调日志和恢复扫描;慢参与者和分区会放大资源占用。 | 容量成本 |
TCC
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| TCC是什么 | Try预留库存/余额/券,Confirm消费预留,Cancel释放预留;协调器保存最终决议并重复二阶段,参与者用事务栅栏幂等。 | 定义、资源模型 |
| Try为什么不能先查再冻结 | 并发Try都可能查到不存在并一起冻结,唯一键冲突发生在副作用之后;应先原子建立分支栅栏所有权。 | 先查后写竞态、Try流程 |
| 空回滚是什么 | Cancel先于Try到达时也写CANCELED屏障,使迟到Try看到屏障后拒绝冻结,否则会留下永远无人二阶段处理的资源。 | 空回滚 |
| 悬挂是什么 | Cancel已完成后迟到Try仍成功执行,资源被挂起且不会再收到正确二阶段;用唯一分支记录和状态条件阻断。 | 悬挂、竞态 |
| 冻结超时能直接释放吗 | 不能,协调器可能已决定Commit只是Confirm延迟;应查询全局决议和业务事实,Commit补Confirm、Rollback补Cancel,未知则待确认。 | 超时释放 |
Saga
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| Saga是什么 | 把长事务拆成已提交的本地步骤,后续失败时对已完成步骤执行幂等业务补偿;不长期持锁,但暴露中间状态。 | 定义、状态模型 |
| 补偿为什么不是数据库回滚 | 正向步骤已经提交,补偿是新业务事实,例如退款新增流水而不是删除支付;补偿也可能失败并需重试/人工。 | 补偿边界、补偿失败 |
| 编排式和协同式区别 | 编排式中央状态机保存流程和补偿,复杂流程可观测性强;协同式服务通过事件推进,自治但流程分散、环路和排查更难。 | 编排、协同、对比 |
| Saga步骤超时能直接补偿吗 | 不能,参与者可能已经提交但响应丢失;先用stepId查询或幂等重试确认正向事实,再按全局决议补偿。 | 原子步骤记录、步骤契约 |
| Saga怎样处理隔离问题 | 使用语义锁、状态标记、版本检查、可交换操作或重读校验,限制其他事务看到并利用中间状态。 | 隔离异常、语义锁 |
Outbox、CDC与事务消息
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| Outbox解决什么 | 业务数据和待发事件同一本地事务提交,关闭“业务成功但没有留下消息意图”的双写窗口;后续投递仍是至少一次。 | 双写问题、原理 |
| Outbox为什么会重复 | Broker已持久化但投递器尚未标SENT就宕机,恢复会再次发送;消费者业务提交后ACK丢失也会重投。 | 发送未知窗口 |
| 多投递器怎样抢任务 | 短事务内用行锁或ownerToken/leaseUntil条件更新领取,提交后再发MQ;租约接管仍可能重复。 | 抢占、租约恢复 |
| CDC Outbox和轮询怎么选 | 轮询简单但有扫描延迟和索引成本;CDC读取提交日志延迟低,但要治理Connector位点、Schema和恢复。 | CDC、方案对比 |
| 事务消息和Outbox区别 | 事务消息通过半消息、本地事务和Broker回查控制可见性;Outbox依赖业务库表与轮询/CDC。两者都不能取消消费者幂等。 | 事务消息、对比 |
| 消费者调用外部HTTP怎样防重复 | 下游支持eventId幂等和结果查询,或消费事务再写Command Outbox;不能在外部调用前先标消费成功。 | 外部副作用 |
Seata
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| TC、TM、RM是什么 | TC保存全局/分支状态并推动二阶段;TM发起Begin/Commit/Rollback;RM登记资源分支、申请全局锁并提交或回滚。 | 角色、生命周期 |
| XID怎样传播 | TM把XID绑定RootContext,Feign/Dubbo等拦截器传给下游,下游线程重新绑定;丢XID的调用只形成普通本地事务。 | XID传播 |
| Seata和Spring本地事务怎么配合 | @GlobalTransactional定义全局事务边界并绑定XID,@Transactional仍管理当前服务当前数据库连接的本地事务,DataSourceProxy在本地提交前生成undo_log、注册分支和申请全局锁。全局事务是跨多个本地事务的协调协议,不会取消本地事务,也不能让未接入RM的远程资源自动回滚。 | Spring事务边界 |
| AT第一阶段怎样执行 | DataSourceProxy拦截SQL,查询前/后镜像,生成undo_log和lockKey,向TC注册分支并申请全局锁,然后业务数据与undo_log同事务提交。 | AT一阶段 |
| AT全局锁和数据库行锁区别 | 本地提交后数据库行锁释放,TC保留逻辑lockKey约束受Seata管理的事务;普通JDBC或DBA写不会自动检查全局锁。 | lockKey |
| AT回滚为什么检查后镜像 | 当前数据等于后镜像才可安全恢复前镜像;不一致说明发生绕过全局锁的写入,盲目覆盖会破坏合法新数据。 | 二阶段回滚、脏数据 |
| AT、XA、TCC、Saga怎么选 | 关系库短事务和受支持SQL可评估AT;资源原生XA且需原子提交选XA;自然预留选TCC;长流程选Saga。 | 选型 |
| AT回滚失败怎么查 | 按XID/branchId查undo_log,比较前镜像、后镜像和当前值,再查主键、权限、锁和绕过代理写入;不能直接删undo_log。 | 故障Runbook |
项目回答模板
核心短事务按资源能力评估XA或Seata AT;库存余额有冻结模型用TCC并处理空回滚、悬挂和幂等;履约长流程用编排式Saga;积分、通知和ES采用Outbox/CDC。任何方案都持久化决议或步骤、使用业务流水幂等,并具备重试、补偿、对账和人工接管。
本章小结
分布式事务面试应从业务不变量、协议状态和失败恢复回答,而不是只列框架名称。
