2. 回滚基础概念

在回滚保护机制设计中,所有复杂策略最终都建立在几个基础概念之上:事务、原子性、一致性、回滚点与补偿操作。它们不是抽象口号,而是决定“失败时系统能否回到可接受状态”的工程约束。理解这些术语之间的边界与配合方式,是后续设计回滚触发条件、保存点策略、补偿幂等性的前提。

一、核心术语的工程含义

事务是一组要么全部生效、要么全部不生效的操作集合。它强调边界:哪些操作属于同一个业务动作,哪些不属于。边界不清,回滚范围就会失控。例如“下单扣库存并生成支付单”应视为一个事务单元,而“发送营销短信”通常不应纳入同一事务,否则短信已发出却因后续失败被回滚,会造成不可逆的外部副作用。

原子性是事务最核心的性质:事务内的操作不可分割。工程上,原子性并不等于“数据库自动帮你搞定”,它需要日志、锁、版本号或补偿机制共同支撑。对于跨服务调用,原子性往往退化为“最终一致性 + 补偿”,而不是强原子。

一致性指事务执行前后,系统满足既定约束。注意,一致性是业务规则层面的,不是单纯的数据相等。例如账户余额不能为负、库存不能超卖、订单状态不能从“已取消”跳回“已支付”。回滚保护的目标,正是当异常发生时,把系统从“违反约束”拉回“满足约束”。

回滚点(Savepoint)是事务内部可回退的中间标记。它允许部分回滚:不必撤销整个事务,只撤销到某个保存点。典型场景是批量处理中,前 100 条已成功,第 101 条失败,可回滚到第 100 条后的保存点,保留已成功部分,避免全量重做。

补偿操作是面向已提交或不可回滚操作的“反向动作”。例如已扣款则退款、已扣库存则回补库存、已发消息则发更正通知。补偿不是回滚,它发生在原操作已经对外可见之后,因此必须考虑幂等、重试与顺序。

事务边界内的原子性与回滚点,边界外的补偿操作 事务边界(原子性作用域) 操作 A 扣库存 操作 B 生成订单 操作 C 写支付单 回滚点 S1 回滚点 S2 失败时回滚到最近保存点,保留已成功部分 补偿操作(边界外) 退款 / 回补库存 反向动作,需幂等 发更正通知 处理不可逆外部副作用 重试 + 对账 保证最终一致 已提交 一致性约束示例: 库存 ≥ 0 | 账户余额 ≥ 0 | 订单状态不可逆跳转 | 支付金额 = 订单金额

二、方法流程:从开启事务到补偿收尾

一个可落地的回滚保护流程通常包含以下步骤:

  1. 界定事务边界:明确哪些操作必须同生共死,哪些可以异步或补偿。
  2. 设置回滚点:在批量或长流程中,按批次或关键节点设置保存点,缩小回滚代价。
  3. 执行操作并记录日志:记录操作类型、参数、版本号、幂等键,为回滚或补偿提供依据。
  4. 异常判断:区分可重试异常、不可重试异常与业务约束冲突。
  5. 回滚或补偿:边界内回滚到保存点或事务起点;边界外触发补偿操作。
  6. 校验一致性:通过对账、状态机校验或约束检查确认系统已恢复一致。

关键原则:能回滚的不要补偿,能补偿的不要重做。回滚成本低于补偿,补偿成本低于人工介入。设计时应优先把操作纳入事务边界,其次才考虑补偿。

三、典型应用场景与参数对照

不同场景对回滚点与补偿的要求差异很大。下表给出常见场景的对照,便于在设计中快速定位策略。

场景 事务边界 回滚点策略 补偿操作 一致性要求
单库批量导入 单事务 每 500 条设保存点 无需补偿 强一致
订单 + 库存 + 支付 跨服务分布式事务 本地保存点 + 全局事务日志 退款、回补库存 最终一致
文件上传 + 元数据写入 对象存储 + 数据库 元数据写入前设保存点 删除已上传文件 最终一致
消息发送 + 状态更新 消息队列 + 数据库 状态更新后设保存点 发更正消息 最终一致

四、案例:批量任务中的保存点与补偿

假设一个批量结算任务需要处理 1000 笔订单,每笔订单包含“扣减余额”和“写入结算流水”两个操作。若不做保存点,第 999 笔失败会导致前 998 笔全部回滚,重做成本极高。合理做法是每 100 笔设置一个保存点:

BEGIN;
FOR i IN 1..1000 LOOP
  IF i % 100 = 0 THEN
    SAVEPOINT sp_batch_i;
  END IF;
  UPDATE account SET balance = balance - amt WHERE id = uid;
  INSERT INTO settlement_log(order_id, amt) VALUES(oid, amt);
  IF error THEN
    ROLLBACK TO SAVEPOINT sp_batch_i;
    -- 记录失败批次,继续下一批
  END IF;
END LOOP;
COMMIT;

如果某些操作已经提交到外部系统(如已调用支付网关),则无法用数据库回滚撤销,必须走补偿:调用退款接口,并记录补偿日志。补偿接口必须支持幂等,通常以“订单号 + 补偿类型”作为幂等键。

常见误区:把“回滚”和“补偿”混为一谈。回滚发生在事务未提交或可撤销阶段,补偿发生在操作已对外可见之后。补偿失败不能简单重试,必须配合对账与人工兜底,否则会出现“退了两次款”或“库存回补但订单仍有效”等不一致。

五、复盘要点

  • 边界优先:先画事务边界,再谈回滚点与补偿,避免把不可逆操作塞进事务。
  • 保存点粒度:太粗则回滚代价大,太细则日志与锁开销高,通常按批次或业务阶段设置。
  • 补偿幂等:补偿操作必须可重复执行且结果一致,否则重试会放大故障。
  • 一致性校验:回滚或补偿后必须验证约束,不能默认“执行了就等于一致”。
  • 可观测性:记录事务 ID、保存点名称、补偿状态,便于排障与对账。

掌握这些基础概念后,后续设计回滚保护机制时,才能准确判断“该回滚到哪、该补偿什么、如何验证一致性”,而不是把所有失败都交给一次全量重做。