1. 课程导入

在银行核心系统中,账务正确性不是“尽量做到”,而是“必须做到”。一笔转账、一笔代扣、一笔冲正,只要被重复执行一次,就可能造成客户资金多扣、多入、总账与分户账不平,甚至触发监管报送差异。幂等与重复记账防护,正是围绕“同一笔业务意图,无论请求到达多少次,最终账务结果只生效一次”建立的一整套工程能力。本课程面向银行账务、支付清算、核心系统研发与测试人员,用30章内容把这件事从概念讲到落地。

学习目标:从“知道幂等”到“能设计防重方案”

本课程不满足于让学员记住“幂等就是多次调用结果一致”这句定义,而是要求达到四个可检验的目标:

  • 概念清晰:能区分幂等、防重、去重、冲正、重放、重试等易混概念,明确各自解决什么问题、不解决什么问题。
  • 机制可设计:能针对转账、代扣、批量入账、冲正、查询等不同场景,选择唯一流水号、幂等表、状态机、乐观锁、分布式锁或组合方案。
  • 故障可定位:面对“客户被扣两次”“总账多一笔”“重试后状态不一致”等生产问题,能沿请求链路、流水号、状态字段和日志快速定位重复发生点。
  • 方案可落地:能输出包含表结构、接口约束、异常分支、对账补偿和监控告警的完整防护设计,而不是只画一张流程图。

核心概念:先厘清“同一笔”与“只生效一次”

幂等的本质是“同一业务意图的重复请求,对系统状态产生的影响与一次请求相同”。这里有两个关键词:同一业务意图和只生效一次。前者要求系统能识别“这是同一笔”,后者要求系统在识别后拒绝或复用首次结果。银行账务中,识别同一笔通常依赖全局唯一业务流水号、请求唯一ID、渠道流水+交易日期等组合键;只生效一次则依赖数据库唯一约束、状态机流转和事务边界。

需要特别区分几个概念:防重是防止重复请求进入账务处理;幂等是即使重复请求进入,结果也不重复生效;去重多指消息或批量层面的重复剔除;冲正是对已生效错误账务的反向处理,它本身也必须幂等。四者相关但不等同,混用会导致设计漏洞。

核心判断句:一笔账务是否安全,不取决于“请求会不会重复”,而取决于“重复请求到达时,系统有没有能力识别并只让首次生效”。

整体框架:四层防护与一条主线

本课程把银行账务幂等与重复记账防护拆成四层:接入层负责请求唯一标识与前置防重;业务层负责幂等判断、状态机与业务规则校验;账务层负责分录唯一约束、余额更新与事务一致性;补偿层负责对账、差错识别、冲正与人工干预。四层不是孤立存在,而是围绕“业务流水号”这条主线串联:从渠道生成、接入校验、业务落库、账务记账到对账核销,同一个流水号贯穿始终。

银行账务幂等四层防护与业务流水号主线 接入层 唯一请求ID / 前置防重 业务层 幂等判断 / 状态机 账务层 分录唯一约束 / 事务 补偿层 对账 / 冲正 业务流水号主线:渠道生成 → 接入校验 → 业务落库 → 账务记账 → 对账核销 重复请求来源 网络重试 / 超时重发 / 消息重投 防护关键点 唯一键 / 状态机 / 事务边界 最终目标 同一意图只生效一次

这条主线意味着:任何一层缺失唯一标识或唯一约束,重复记账就可能穿透。例如接入层生成了请求ID但业务层未落库,重试时业务层无法判断是否处理过;账务层有唯一约束但业务层状态未更新,冲正时又可能重复冲正。因此本课程强调“端到端防重”,而不是单点幂等。

方法流程:一笔账务从请求到生效的防重路径

以一笔跨行转账为例,典型防重路径如下:

  1. 渠道生成业务流水号:由发起渠道生成全局唯一流水号,作为整笔业务的“身份证”。
  2. 接入层校验并登记:检查流水号是否已存在,若存在直接返回首次结果;若不存在则登记请求记录。
  3. 业务层幂等判断:查询幂等表或业务主表,按流水号判断状态是“处理中”“成功”还是“失败”。
  4. 账务层唯一约束:分录表以流水号+分录序号建立唯一索引,重复插入直接触发唯一键冲突。
  5. 事务提交与状态更新:账务成功与业务状态更新在同一事务内完成,避免“账记了状态没改”。
  6. 异常分支处理:超时、冲突、部分成功分别走查询、复用结果或补偿流程。
-- 幂等表关键结构示例
CREATE TABLE t_idempotent_record (
  biz_no        VARCHAR(64)  NOT NULL COMMENT '业务流水号',
  request_id    VARCHAR(64)  NOT NULL COMMENT '请求唯一ID',
  status        VARCHAR(16)  NOT NULL COMMENT 'PROCESSING/SUCCESS/FAIL',
  result_code   VARCHAR(16)           COMMENT '首次处理结果码',
  created_time  DATETIME     NOT NULL,
  updated_time  DATETIME     NOT NULL,
  PRIMARY KEY (biz_no),
  UNIQUE KEY uk_request (request_id)
) COMMENT='幂等登记表';

这段结构的关键在于:biz_no作为主键保证同一笔业务只有一条记录,request_id唯一索引防止同一请求重复登记。当重复请求到达时,插入冲突即可作为“已处理”的信号,而不是继续记账。

案例与场景:重复记账通常从哪里来

生产中的重复记账很少是“有人故意点两次”,更多来自系统行为:

  • 超时重试:上游调用账务超时,但账务实际已成功,上游重试导致二次记账。
  • 消息重投:MQ消息因消费失败或确认丢失被重新投递,消费者未做幂等。
  • 批量任务重跑:日终批量入账失败后重跑,未按批次号或明细号去重。
  • 人工冲正重复:柜面或运营发起冲正,未校验原交易是否已冲正。
  • 分布式并发:同一笔请求被多个节点同时处理,数据库唯一约束未覆盖。

高风险误区:只在前端按钮置灰或只靠“查询再插入”防重。前者无法防接口重放,后者在并发下存在检查与插入之间的时间窗,仍可能重复。真正可靠的防线必须落到数据库唯一约束和事务边界上。

实战价值与常见误区复盘

掌握本章内容后,学员应能回答三个问题:这笔业务的“同一笔”标识是什么?重复到达时系统在哪一层拦截?如果拦截失败,补偿层如何发现并纠正?这三个问题贯穿后续所有章节。

防护层次 核心手段 典型失效原因 复盘要点
接入层 请求唯一ID、前置防重 ID由上游重复生成 确认ID生成规则与传递链路
业务层 幂等表、状态机 状态更新与账务不在同一事务 检查事务边界与状态流转
账务层 唯一索引、乐观锁 唯一键未覆盖业务维度 核对唯一键组合是否完整
补偿层 对账、冲正、告警 对账规则遗漏、冲正不幂等 验证冲正是否可重复执行

学习建议:每学完一种机制,回到自己系统中的一笔真实交易,画出它的流水号传递路径和唯一约束位置。能画清楚,才算真正掌握;画不清楚,说明防护还有盲区。