3.6 批量操作与事务处理:契约的组合动作


3.6 批量操作与事务处理:契约的组合动作

本节摘要:列表接口批量增删改、跨资源的一致性动作,是契约里"一次干多件事"的组合动作。本节讲清批量操作的统一语义(复合文档、批量端点、部分成功),并用一个"多状态更新"案例对比本地事务与分布式事务/补偿策略的取舍,最后给出批量接口的幂等性设计。

学习目标

阅读完本节,你应当能够:

  1. 用"复合文档 / 批量端点 / 部分成功"设计一套批量操作语义。
  2. 在本地事务、两阶段提交、补偿式事务之间做取舍。
  3. 设计批量接口回报"部分成功"的响应结构。
  4. 说明批量操作的幂等边界与防重复。

批量操作和单条不是一个难度

POST /users 建一个用户不难。但要"一次建一百个、或一个请求改掉十个订单状态"时就遇上新问题:每条可能部分成功、失败,事务边界跨越多个资源,客户端不知道"哪些成了哪些没成"。批量操作是契约里的组合动作——做对是省事,做错是数据混乱的温床。

批量的收益和代价要一起算:它省的是"网络往返次数"和"服务端的逐条开销",但一行巨大请求体同样会拖垮网关与超时。所以批量接口通常要约定上限(比如一次最多 100 条),超出则让客户端拆批,而不是狠心吃下无限大的数组。设计时把"单批上限 + 返回超限错误"定进契约,比在运行时报 413 让人摸不着头脑要稳得多。

二、批量语义怎么设计

推荐三种方案的组合拳:

  1. 复合文档:请求体是一个数组,包含多条要处理的对象。
    {"items":[{"email":"a@x.com"},{"email":"b@x.com"}]}
  2. 批量端点:在集合上提供批量创建/更新的语义,通常用 POST /users/batch 或复用方法加批量参数。
  3. 部分成功:响应结构化地回执"哪些成功、哪些失败、各是什么错误",而不整体 200/500 一刀切。

部分成功的关键在响应结构,既要整体状态码又要条级明细:

{ "summary": {"succeeded": 2, "failed": 1}, "results": [ {"index": 0, "status": "created", "id": 101}, {"index": 1, "status": "created", "id": 102}, {"index": 2, "status": "failed", "code": "EMAIL_TAKEN", "reason": "邮箱已存在"} ] }

客户端能据此逐条处理:把 index=2 的失败高亮给用户,其余照常。

部分成功还牵扯一个状态码上的选择。整体到底回什么?常用的两个口径并存:整体回 200 但用 summary 说明有失败,或者一律回 207 Multi-Status 让"成与败并存的批量"有专用的语义。无论你选哪一个,契约里要固定只用一个口径,否则客户端不知道"到底看成败在 summary 还是看整体状态码"。还有个常被忽略的点:results 的顺序必须与请求体里的 items 一一对应,客户端才能靠 index 把失败准确定位到原条目——顺序一旦被打乱,index 就成了废索引,逐条对齐也会全盘错位。

三、事务边界:一张取舍表

批量把"一致性"顶到了台前:十来条要么都成、要么都别成(原子),还是允许部分成(非原子)?这要看业务能不能容忍中间态,给出三种典型策略对照:

策略 一致性 代价 适用
本地事务 原子、强一致 仅限单库 单服务单库的批量写
两阶段提交 强一致、跨资源 锁与协调重、脆弱 强一致要求高的短期跨资源操作
补偿式事务 最终一致 流程长、要写补偿逻辑 跨服务、长流程的分布式场景

本地事务最省心:一个数据库里把多条插入放在同一事务里,任意一条失败则整体回滚。两阶段提交能在跨库时强一致,但准备/提交两步的协调开销和锁成本高、还容易挂死。补偿式事务适合跨多个微服务的长时间流程:先各干各的乐观推进,某步失败再反向执行"撤销"动作(如扣款失败则给订单回滚状态),接受"最终一致、中间可能短暂不一致"。

四、一次多状态更新的取舍演练

背景:电商批量改一批订单的状态(待支付 → 已支付)。

操作:批量端点收到 ["待支付", "待支付", "已支付"] 中的前两条要改为已支付。

两条路

  • 本地事务:这些订单都在同一库,直接 UPDATE ... WHERE id IN (...) AND status='待支付',事务一次提交,命中数即成功数——原子、一致、最省心。
  • 补偿式:若这批订单分散在不同服务的表里,就得逐个服务更新,某步失败后对已更新的做回滚(把已支付的订单状态改回待支付)。接受中间短暂不一致,最终对齐。

结果与解读:单库走本地事务,多服务走补偿式,判断依据就一条——"事务边界能不能停在单个数据库里"。别再为跨服务硬上两阶段提交去背锁与协调的重负,除非业务真的不能接受任何中间态。

五、批量接口的幂等与防重复

批量承接了大量写,幂等更不能含糊:给批量请求带幂等键(复用 2.7 的思路),服务器对同一批多次提交只执行一次;复合文档里逐条给唯一索引,重复条目用键去重。否则一次网络重试,就把整批重复写进去。

这里有个被反复问到的坑:批量幂等键该绑在整批还是逐条?默认绑整批——一个键代表"这一次批处理",服务器只要记住"这张键处理过、结果快照在",重放直接回快照即可。只有当你需要"允许同一批里某几条被分开重试"时,才细化到逐条键,但那会让去重复杂度成倍上升。多数场景整批一个键就够,复合文档里再靠逐条唯一约束防止批内重复,两层各管各的,不必为了"灵活"把键拆得太碎。

⚠️ 常见坑:批量接口整体返回 500 却不带"哪几条成了"。部分成功被淹没,客户端无从补救。宁愿把"部分失败"明明白白报出来,让客户端逐条对齐。

💡 关键直觉:批量与事务的取舍,本质是在"原子性"和"可用性/复杂度"之间对赌。交易的原子性越强,协调成本越高;能停在单个事务边界的,就别为跨服务背重负。

本节要点回顾

  • 要点一:批量用复合文档 + 批量端点的组合语义承载。
  • 要点二:部分成功的响应结构化回执逐条成败,别整体 500 一刀切。
  • 要点三:本地事务单库原子最省心,两阶段提交重,补偿式适合跨服务长流程。
  • 要点四:事务边界停在单个数据库内就本地事务,跨就补偿。
  • 要点五:批量写要配幂等键与逐条去重,防网络重试搞成批量重复。
  • 要点六:本质是原子性与复杂度之间的对赌。

六套加固件全部装齐,契约已足够成熟、耐事故。下一章我们把它拉到车间四——做成文档、序列化、测过、送上网关与 CORS 配置,真正落地交付。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U