你打开演唱会购票 App,抢到一张心仪已久的门票。表面上看,你只是点了几下屏幕,可背后,三个独立的业务系统正在你追我赶:票务系统要锁住一个座位,支付系统要从你的银行卡里扣钱,会员系统要给你的账户加积分——它们各自维护着自己的数据库,谁也没法替别人做主。 如果这三个动作必须“永不反悔”地一起成功,可就难了:网络抖动一下、服务器卡顿一秒,都可能出现“座位锁了、钱没扣成”的尴尬局面。聪明人想出一个办法:把“买票”拆成三步——先占住座位但不出票(Try),确认无误后再出票扣款(Confirm),任何一步不顺就把座位放回去(Cancel)。这三步合起来,就是今天要讲的 TCC 补偿事务。它不追求所有系统“同时”成功,而是保证最终大家能对得上账——这就是最终一致性。
一句话价值:掌握 TCC,你就掌握了微服务架构下跨库数据最终一致性的核心武器,是胜任资深后端与 AI 平台工程师的必备技能。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
TCC补偿事务三阶段流程 Try阶段预留资源(冻结余额、预扣库存),不真正执行业务操作 Confirm阶段确认执行消费预留资源;Cancel阶段释放预留回滚 空回滚处理Try未到达异常,悬挂防护避免Cancel后Try迟到 TCC通过业务层补偿实现最终一致性
分布式困境
演唱会门票销售从线下搬到线上后,事情变得复杂了。你在官方网站上买票,看似一次点击,背后其实是三个“收银台”在协作:票务柜台负责锁定座位,支付柜台负责从卡里扣钱,账户柜台负责给会员积分加分。更麻烦的是,这三个柜台各自拥有一本完全独立的账本,谁也管不了谁。一旦网络抽风,就会出现“票务柜台划掉了座位,支付柜台却说钱没扣成”的扯皮场面。
在传统单体系统里,一个客户买票,减库存、扣余额、加积分都在同一个数据库里完成。数据库提供的 ACID 事务——原子性、一致性、隔离性、持久性——可以保证这组操作要么全部成功、要么全部失败。可一旦拆成微服务,三个操作落在三个不同的数据库里,本地事务就“手伸不到别人家”了。这就是分布式事务要解决的难题:如何让多个独立数据库的操作最终保持一致。
为什么跨库这么难?因为网络会超时、服务会宕机、消息会丢失,任何一个环节出错,其他环节已经执行的操作都无法自动撤回。假设每个独立环节的成功概率都是 p,一个需要 n 个环节的事务,整体成功率会快速衰减:环节越多,失败概率越高,数据不一致的风险就越大。这正是分布式环境下“越拆分越脆弱”的数学真相。
这就像三个柜台各记各的账:票务柜台已经划掉了座位,支付柜台却说“网络断了,钱没扣成”,两边账本对不上,你说该信谁?TCC 的出现,就是给这三个柜台立一套统一的“对账规矩”,让它们在同一套规则下协作而不是互相甩锅。
- P_success(整体成功率)整个跨库事务从第一笔操作到最后全部成功的概率,相当于三个柜台全部不出错的概率。
- 连乘符号 ∏把 n 个环节的成功率逐一相乘,表示“每个环节都必须成功,缺一不可”。
- p_i(单环节成功率)第 i 个独立环节(如库存锁定、余额扣减)的成功概率,通常由单机稳定性决定。
- p^n(指数衰减)当所有环节成功率同为 p 时,整体成功率等于 p 的 n 次方。n 越大数字越小,说明环节越多越脆弱。
三阶段原理
这套规矩的核心,是把一次“买票”拆成三步:先把座位“占住”但不出票(Try),占住了再正式“出票”(Confirm),占不住或你自己放弃,就“放回去”(Cancel)。注意,这三步不是数据库自动完成的,而是每个业务系统都要亲手实现的三段代码。Try 是试探,Confirm 是拍板,Cancel 是变卦。
对应到技术上,TCC 要求每个参与方都实现 Try、Confirm、Cancel 三个方法。Try 阶段做资源预留——库存服务冻结 1 件商品、钱包服务预扣 99 元,但商品还没真的卖出去、钱还没真的划走。Confirm 阶段把预留变成现实——库存减一、钱包减钱。Cancel 阶段把预留归还——库存加回、钱包退款。整个过程由事务管理器(协调者)统一编排。
TCC 的关键在于 Try 这道“保险闸门”:无论后续走 Confirm 还是 Cancel,都在 Try 的预留基础上操作,绝不会出现“超卖”或“重复扣款”。只有当所有参与方的 Try 都成功,协调者才统一发起 Confirm;只要有一个 Try 失败,或业务中途取消,协调者就对已成功的参与方逐个发起 Cancel。这可以用一个条件决策式表达:
三兄弟里,Try 是最稳重的那个——它从不真正“搞定”任何事,只负责把位置占住;Confirm 和 Cancel 则像一枚硬币的两面,只能抛出一面,绝不会同时出现。正是这种“先占位、再决定”的设计,让 TCC 能在不锁数据库的情况下保证最终一致。
- Try(s)用业务参数 s 调用每个参与方的预留方法,比如“锁定座位 2 张”,此时数据处于冻结态而非生效态。
- if Try(s) succeeds(Try 成功)所有参与方都预留成功,协调者拍板走 Confirm 分支。
- Confirm(Try(s))在预留的资源上执行确认,把“冻结 2 张”变成“已售 2 张”,业务真正生效。
- Cancel(Try(s))当任一 Try 失败或业务中途取消时,对已预留资源执行释放,退回到初始状态。
幂等设计
假设你在付款页面网络卡了一下,手指一急连点了三次“确认支付”。你是该被扣三笔钱,还是只扣一笔?显然,无论点多少次,同一笔订单只能出一张票、扣一次钱。这个“多点几次和点一次结果一样”的性质,就叫幂等。它是 TCC 能在不可靠网络上生存的基石。
分布式环境下,消息重发是家常便饭:Confirm 请求可能在网络上走丢了,协调者自动重试;Cancel 请求也可能重复投递。如果参与方不做好幂等,就会出现“同一张票被出两次”或“同一笔退款退两遍”的灾难。所以 TCC 的每个方法都必须能识别“这单我是不是已经处理过了”。
实现幂等要靠两件东西:一是全局唯一的事务 ID,相当于每笔交易的“身份证号”;二是状态机校验——参与方在执行业务前,先查本地事务记录:如果该事务 ID 已经处于终态(已提交或已取消),直接返回成功,不再重复执行。用数学语言说,业务操作 f 满足执行两次等同执行一次:
这就像自助售货机:你投了硬币,多拍几下“出货”按钮,它也只会掉一罐饮料——因为机器内部早就给这笔交易盖了“已出货”的章。TCC 中的事务控制表就是这个“章”,一盖下去,重复请求统统放行,但绝不重复执行。
- f(x)(一次业务操作)对数据 x 执行一次完整业务动作,例如对订单 1001 扣款 99 元。
- f(f(x))(重复执行)用同样的参数再执行一次,模拟网络重试、消息重复投递的真实场景。
- 等式成立(幂等)两次执行的结果与一次执行完全一致,说明该操作满足幂等性,不会重复生效。
异常处理
分布式世界里总有“幽灵事件”。你明明点了取消,系统却回你一句“取消失败,因为没找到你的订单”。其实不是系统傻了,而是 Cancel 请求到达时,Try 可能还没来得及执行——这在 TCC 中叫“空回滚”:没有预留可释放,取消却已经来了。这是合法路径,不是 BUG。
与空回滚对应的还有“悬挂”:Confirm 已经成功完成,但因为响应超时,发起方以为它失败了,于是发出 Cancel。如果 Cancel 真的去“补偿”一笔已经确认的交易,就会造成账目错乱。此外还有 Try 卡死、网络分区等超时场景。这些异常的本质,都是消息到达顺序与真实执行顺序不一致。
处理这些异常的核心手段是“记账 + 拦路”。每个参与方用事务控制表记录该事务 ID 的完整状态流转:收到 Cancel 时先查表,如果根本没有 Try 成功的记录,就静默返回成功(空回滚);如果状态已是终态,就拦截迟到的 Cancel 消息(防悬挂)。竞态窗口的大小可以用下面这个式子刻画,它决定了悬挂风险的时间跨度:
对付幽灵最好的办法,是让每一个操作都留下记录。柜台翻一翻账本,发现“你这单根本没占座”,直接把你的取消请求当作“已办结”处理——不纠缠、不报错,事情就平息了。这就是异常处理的艺术:不是消灭所有异常,而是让异常在规则下安全落地。
- t_confirm(确认完成时刻)Confirm 操作真正落库并记为终态的那一刻,是数据生效的分水岭。
- t_cancel(取消发出时刻)发起方因为超时等原因发出 Cancel 请求的那一刻。
- 差值(竞态窗口)Cancel 发出时间减去 Confirm 完成时间。正数意味着 Cancel 晚于 Confirm 到达,存在悬挂风险。
- max(0, ·)(取非负)时间差为负数时取 0,表示 Cancel 到来之前 Confirm 已确认,悬挂窗口闭合。
落地实践
实操中,TCC 需要一本“总台账”来协调所有参与方,这就是事务控制表。每笔交易一行记录:事务 ID、参与方列表、当前状态(初始、已 Try、已 Confirm、已 Cancel)、操作时间。任何一个参与方想动数据,先查台账,再按状态机规则流转——先 Try 再 Confirm 或 Cancel,绝不能凭空起飞。
状态机规定了合法流转路径:初始 → 已 Try → 已 Confirm,以及初始 → 已 Try → 已 Cancel。任何偏离路径的请求都被视为非法,直接拒绝。比如收到 Confirm 但状态还是“初始”,说明消息乱序或伪造,必须拦截。这种“查表 + 校验”的模式,让消息即使乱飞,数据也走不歪。
此外还要做并发控制。同一件商品可能被两个用户同时 Try,必须用唯一索引加状态字段或乐观锁(版本号)保证同一资源只能被一个事务预留。对长时间停留在“已 Try”中间态的事务,要配置监控告警,由定时任务自动补偿或人工介入。整套机制可以用一个状态转移函数抽象描述:给定当前状态和到达事件,得到合法的新状态。
有了总台账和状态机,整个系统就像装上了红绿灯和监控探头:每一辆“事务列车”都按轨道行驶,一旦有车在中间路段滞留,系统立刻报警,调度员马上人工处理。这样,TCC 就不再是纸面理论,而是可以稳定运行的工程系统。
- S_current(当前状态)事务在控制表中的当前状态,如初始、已 Try 等。
- E(触发事件)到达的消息或超时信号,如 Try 成功、Confirm 请求、Cancel 请求。
- g(状态转移函数)根据当前状态与事件查状态机表,得到合法的下一状态,非法事件直接拒绝。
- S_next(下一状态)状态机校验通过后事务进入的新状态,如已 Confirm、已 Cancel。
场景与权衡
TCC 虽好,却不是万能药。买一瓶矿泉水扫码支付,一个数据库本地事务就够了,硬要拆成 Try-Confirm-Cancel,反而是杀鸡用牛刀。TCC 的真正主场,是那些要跨库修改“稀缺资源”的业务:库存会不够、余额会不足、票数会耗尽——这些资源一旦超卖或透支,后果很严重。
适合 TCC 的业务有三个特征:资源型(库存、余额、配额)、强约束(不能超卖、不能透支)、可补偿(失败后能释放资源)。纯查询、只读报表、日志收集这类操作没有“资源锁”的概念,就完全不需要 TCC。识别这个边界,才能避免过度设计。
TCC 与 2PC 的取舍也很清晰:2PC 让协调者在 prepare 阶段持有数据库锁直到 commit,性能差、可用性低;TCC 把 try 阶段的锁换成业务层面的“冻结”,长事务期间不占数据库锁,性能更好,但代价是每个参与方都要手写三套业务方法,开发成本高。整体耗时大致等于 Try 阶段耗时,加上 Confirm 与 Cancel 二者中较长的一条路,再加上网络往返开销:
所以记住这句口诀:能用账本自己解决的事,别惊动三兄弟;只有当多个独立账本必须保持步调一致时,才请 Try、Confirm、Cancel 出山。选型没有绝对的好,只有合不合适——TCC 为“强资源约束 + 高并发”而生,它追求的不是万无一失的强一致,而是实用的最终一致。
- T_try(预留阶段耗时)所有参与方 Try 串行或并行预留资源的总耗时,只做冻结不落死账。
- max(T_confirm, T_cancel)(二选一耗时)Confirm 与 Cancel 只会走其中一条,取两者中较大者代表最终完成路径的耗时。
- T_network(网络往返开销)三次调用之间网络传输与协调者的调度开销,在分布式环境下不可忽略。
AI 应用场景
电商订单履约:下单、锁库存、扣款、加积分一把梭
阶段:跨服务数据一致性保障
解决的问题:下单涉及库存、钱包、积分三个微服务各写各的库。同一笔订单,如果库存扣了但扣款失败,或扣款成功但积分没加,都会造成资损或用户体验下降。传统无分布式事务的方案只能靠售后对账人工修复;用 TCC 后,三个服务先各自预留,全部成功才真正生效,任一失败自动补偿回初始态,账实始终对得上。
具体案例:某电商大促 1 秒售出 3 万单。用户买一台 6999 元的手机:库存服务 Try 冻结 1 台,钱包服务 Try 预扣 6999 元,积分服务 Try 预加 699 积分。三个 Try 全部成功,协调器统一发起 Confirm;若钱包余额不足导致 Try 失败,协调器自动对已成功的库存服务发 Cancel,释放库存。
AI 平台多租户资源计费与配额结算
阶段:分布式任务结算
解决的问题:AI 训练平台里,一次任务同时消耗 GPU 机时、对象存储容量和账号余额。任务跑完要同时扣三本账,任何一个扣费服务超时都会导致记账不平,影响对账与成本核算。TCC 让每个计费参与方先预留再确认,任务失败时统一回滚,保证平台账实相符。
具体案例:某 MLOps 平台每天调度 5000 个训练任务。任务结束时计费系统 Try 冻结费用、配额系统 Try 扣减 GPU 配额、存储系统 Try 记录用量;全部 Try 成功则 Confirm,任务失败重跑则全部 Cancel 回滚配额与费用,月末对账零差异。
Python 演示
# -*- coding: utf-8 -*-"""TCC 补偿事务简易模拟器: 用演唱会抢票演示三阶段流程"""import uuid class TicketService: """票务服务: 管理库存, 提供 Try/Confirm/Cancel 三阶段能力""" def __init__(self, total=100): self.total = total self.frozen = 0 self.sold = 0 self.tx_records = {} # 事务ID -> 状态 def try_op(self, tx_id, num): if self.tx_records.get(tx_id) == "TRIED": return True # 幂等: 同一事务的重复 Try 直接返回成功 if self.total - self.sold - self.frozen < num: return False # 库存不足, 预留失败 self.frozen += num self.tx_records[tx_id] = "TRIED" return True def confirm(self, tx_id, num): if self.tx_records.get(tx_id) == "CONFIRMED": return True # 幂等: 已确认过的订单不重复处理 if self.tx_records.get(tx_id) != "TRIED": return False # 状态校验: 只有 Try 成功后才能 Confirm self.frozen -= num self.sold += num self.tx_records[tx_id] = "CONFIRMED" return True def cancel(self, tx_id, num): if self.tx_records.get(tx_id) != "TRIED": return True # 空回滚: Try 未执行就收到取消, 视为成功 self.frozen -= num self.tx_records[tx_id] = "CANCELED" return True class PaymentService: """支付服务: 模拟预扣款与退款""" def __init__(self, balance=100000): self.balance = balance self.pay_records = {} def try_op(self, tx_id, amount): if self.pay_records.get(tx_id) == "TRIED": return True if self.balance < amount: return False self.balance -= amount # 预扣到虚拟冻结账户 self.pay_records[tx_id] = "TRIED" return True def confirm(self, tx_id, amount): if self.pay_records.get(tx_id) == "CONFIRMED": return True if self.pay_records.get(tx_id) != "TRIED": return False self.pay_records[tx_id] = "CONFIRMED" # 预扣已生效, 只更新状态 return True def cancel(self, tx_id, amount): if self.pay_records.get(tx_id) != "TRIED": return True # 空回滚: 未预扣成功, 无需退款 self.balance += amount # 退还预扣款 self.pay_records[tx_id] = "CANCELED" return True def run_tcc(services, params, fail_step=None): """编排 TCC 流程: 全部 Try 成功则 Confirm, 否则逆序 Cancel""" tx_id = uuid.uuid4().hex[:8] tried = [] for i, (service, param) in enumerate(zip(services, params)): if fail_step is not None and i == fail_step: break # 模拟第 i 步 Try 阶段网络异常 if not service.try_op(tx_id, param): break tried.append((service, param)) else: for service, param in tried: service.confirm(tx_id, param) return tx_id, "CONFIRMED" for service, param in reversed(tried): service.cancel(tx_id, param) # 补偿已成功的参与方 return tx_id, "CANCELED" if __name__ == "__main__": ticket = TicketService(total=100) payment = PaymentService(balance=100000) print("== 场景1: 正常购票 ==") _, st = run_tcc([ticket, payment], [2, 1000]) print("状态:", st, "| 已售:", ticket.sold, "| 冻结:", ticket.frozen, "| 余额:", payment.balance) print("== 场景2: 库存不足 ==") _, st = run_tcc([ticket, payment], [200, 1000]) print("状态:", st, "| 已售:", ticket.sold, "| 冻结:", ticket.frozen, "| 余额:", payment.balance) print("== 场景3: 支付环节 Try 失败 ==") _, st = run_tcc([ticket, payment], [2, 1000], fail_step=1) print("状态:", st, "| 已售:", ticket.sold, "| 冻结:", ticket.frozen, "| 余额:", payment.balance)
if self.total - self.sold - self.frozen < num:检查当前可售余量,只有真正有余票才允许冻结。self.frozen += numTry 阶段只增加冻结数,不减少总量,这就是“预留而非消耗”。if self.tx_records.get(tx_id) == "CONFIRMED": return True已确认过的订单直接返回,实现 Confirm 幂等。if self.tx_records.get(tx_id) != "TRIED": return True空回滚处理:Try 从未成功就收到取消,静默成功。self.balance -= amount # 预扣到虚拟冻结账户支付 Try 先把钱从可用余额划走,相当于“冻结资金”。tried.append((service, param))记录所有 Try 成功的参与方,为后续统一 Confirm 或补偿 Cancel 做准备。for service, param in reversed(tried): service.cancel(tx_id, param)逆序补偿:先回滚后成功的参与方,避免依赖顺序问题。
常见误区
误区“2PC 就是 prepare/commit 两个阶段,TCC 就是 Try/Confirm 两个阶段,不就是换个名字嘛?”初学者常把两者混为一谈。
正解两者本质不同。2PC 是数据库层面的 XA 协议,协调者在 prepare 阶段让所有库持有锁并同步等待,任何一台机器失联整个事务就会卡死;TCC 的三个阶段全部在业务代码里实现,Try 阶段只用“冻结”这类业务动作预留资源,不持有数据库锁,参与方之间也无需同步阻塞,灵活性和性能都更好。就像 2PC 是“全场拉闸停电、统一宣布结果”,TCC 是“各家先占座、后单独办理”。
为何常见:两者都是“先尝试、后决定”的两段式流程,阶段数量相同,概念上容易先入为主。
误区“Try 失败了直接 rollback 不就完了吗?为什么还要自己写 Cancel?”很多入门开发者把 TCC 的 Cancel 等同于数据库回滚。
正解本地事务的 rollback 是数据库自动撤销未提交的修改;而 TCC 中 Try 阶段的“预留”是一个独立的、已提交的业务操作,数据库并不理解“冻结”和“扣减”之间的业务关系。Cancel 必须由开发人员编写反向业务逻辑,比如把冻结的库存加回去、把预扣的钱退回来,属于业务补偿而非机械回滚。
为何常见:习惯了单体数据库事务的自动回滚语义,忽略了分布式环境下没有全局 undo 机制。
误区“三个服务的 Try 都成功了,这笔交易肯定成了,Confirm 随便出个票就行。”很多初学设计者把 Confirm 当成可有可无的摆设。
正解Confirm 同样会因网络超时、服务宕机、消息丢失而失败。因此 Confirm 也必须具备幂等性,配合消息队列与重试机制确保最终完成;如果重试后仍失败,还要触发补偿流程(Cancel 部分已确认的资源)或人工介入,绝不能默认“Try 成功即提交成功”。
为何常见:Try 是资源能否预留的关键,容易让人误以为后面只是走个过场。
误区“收到 Cancel 却查不到 Try 记录,这不是系统 BUG 吗?应该抛异常让上层知道!”——把空回滚当作程序故障。
正解空回滚是 TCC 的合法语义。Try 请求可能因网络超时从未到达参与方,但协调器超时后已发起 Cancel。此时参与方应当把取消当作“该事务从未发生过”来静默成功,确保事务快速终结;如果抛异常,协调器会反复重试,最终造成悬挂或死锁。
为何常见:本地事务中“操作不存在就是异常”,但分布式环境下“消息乱序”本身就是常态,需要换一套思路。
面试题
Q1. TCC 的 Try 阶段核心目标是什么?
- A.直接完成业务扣减
- B.预留资源但不生效← 正确答案
- C.释放所有已锁定资源
- D.提交整个分布式事务
答案:B。Try 阶段的核心是“冻结”而非“扣减”,比如锁住座位但不真正出票。选 B。A 是 Confirm 的职责,C 是 Cancel 的职责,D 是协调者的整体职责。
Q2. TCC 的 Confirm 阶段执行条件是什么?
- A.所有参与方 Try 全部成功← 正确答案
- B.任意一个 Try 成功即可
- C.由数据库自动触发
- D.提交时可绕过 Try 直接执行
答案:A。只有所有参与方的 Try 都成功,协调者才会统一发起 Confirm。就像三个柜台都完成了“占座”,才能正式出票。B 缺一不可,C 是 2PC 的误区,D 违背状态机规则。
Q3. 以下哪个场景最适合使用 TCC?
- A.大数据平台离线报表聚合
- B.订单库存扣减和账户余额扣款← 正确答案
- C.搜索引擎索引的批量重建任务
- D.静态前端资源的 CDN 缓存刷新
答案:B。TCC 适合“资源型、强约束、可补偿”的业务,库存和余额正是典型。A、C、D 都是无资源锁定的计算或缓存任务,用本地操作即可,不需要三阶段。
Q4. “空回滚”指的是什么场景?
- A.Try 成功后 Confirm 失败
- B.取消时发现 Try 从未成功← 正确答案
- C.Confirm 被重复执行两次
- D.Cancel 释放了过多资源
答案:B。空回滚是 Cancel 到达时,事务控制表中没有 Try 成功的记录。此时应静默成功而非报错。A 是 Confirm 失败,C 是幂等问题,D 是补偿过度。
Q5. 幂等操作的数学性质是?
- A.两次执行效果等同一次← 正确答案
- B.两次执行效果互相抵消
- C.多次执行结果趋于零
- D.执行次数越多结果越大
答案:A。幂等要求 f(f(x)) = f(x),无论执行多少次结果都一样。B 是互逆操作,C 和 D 都违背了“结果稳定”的本质。
Q6. 为什么 TCC 的 Cancel 不能依赖数据库 rollback?
- A.数据库引擎不支持回滚操作
- B.Try 的改动已独立提交到业务库← 正确答案
- C.Cancel 必须额外释放数据库锁
- D.回滚会造成全局数据库死锁
答案:B。Try 阶段的“冻结”是已提交的业务操作,数据库不知道它是预留,无法自动撤销。Cancel 要靠开发人员写反向业务逻辑,把冻结释放。A、C、D 都是对数据库机制的误解。
Q7. 解决 TCC “悬挂”问题的关键手段是什么?
- A.让 Cancel 消息无限次重试发送
- B.用控制表和幂等键拦截迟到请求← 正确答案
- C.完全禁止使用任何超时重试机制
- D.提高 Try 成功率到百分之一百
答案:B。悬挂是迟到的 Cancel 与已完成的 Confirm 竞争。核心手段是事务控制表加状态机拦截:一旦状态是终态,迟到的 Cancel 直接忽略。A 会让问题更严重,C 和 D 不现实。
Q8. TCC 相比 2PC 的优势包括哪些?
- A.业务层预留资源不长期锁库← 正确答案
- B.协调者无需做任何超时处理
- C.不依赖数据库级分布式锁← 正确答案
- D.完全不考虑网络异常的情况
答案:AC。TCC 在业务层做冻结,不占数据库长锁(A 对);同样不依赖数据库 XA 锁(C 对)。但超时处理依然必要(B 错),网络异常也需要重试和补偿机制(D 错)。
Q9. 以下哪些属于 TCC 的典型异常场景?
- A.空回滚:Try 从未成功却收到取消请求← 正确答案
- B.悬挂:迟到的取消与已完成确认竞争← 正确答案
- C.重复投递:同一条确认消息被重试多次← 正确答案
- D.所有数据必须在同一个数据库内提交
答案:ABC。A 是空回滚,B 是悬挂,C 是消息重复,都是 TCC 必须处理的典型异常。D 描述的是单体数据库方案,与 TCC 无关。
Q10. 落地 TCC 时通常需要哪些配套机制?
- A.引入事务控制表记录每个事务状态← 正确答案
- B.用状态机校验非法状态流转← 正确答案
- C.对长期中间态事务配置监控告警← 正确答案
- D.用全局分布式锁串行化全部操作
答案:ABC。事务控制表(A)、状态机校验(B)、监控告警(C)是 TCC 落地的三件套。D 用全局锁串行化所有操作会杀掉并发性能,违背 TCC 的设计初衷。