第 10 / 22 篇

第 9 章:唯一业务键与库存变化共同提交

用户点击两次,网络层重发一次,任务恢复又执行一次,同一个业务意图可能多次到达数据库。幂等需要有稳定的业务身份,以及围绕这个身份建立的约束和结果查询。

9.1 查询不存在不能代替唯一键

两个事务可以同时查询某个订单键不存在,再各自尝试插入。真正裁决重复的是数据库唯一约束。应用应能识别预期的重复键冲突,并查询该业务键已有的结果,而不是把所有唯一冲突都描述成成功。

幂等键需要作用域,例如租户、业务类型和操作标识。还要核对参数:同一个键第一次购买商品 A,第二次却变成商品 B,不能仅因键相同就无条件返回第一次的成功。

9.2 把键与扣减放在同一事务

随书实验先插入唯一操作键,再执行条件扣减,然后提交。两个线程使用相同键,一个完成,另一个收到 1062 并回滚。最终只有一个订单,库存只扣减一次。

START TRANSACTION;
INSERT INTO orders(op_id,qty) VALUES('same-operation',1);
UPDATE inventory SET stock=stock-1 WHERE sku=1 AND stock>0;
-- 确认受影响行数为1,否则回滚
COMMIT;

不能把插入幂等记录单独先提交,再让库存操作在另一笔事务里失败;那样重复请求会查到一个“存在但业务没完成”的记录。若业务确实需要多阶段状态,应明确保存 pending、completed 等含义与恢复动作。

9.3 字符比较规则也是合同

业务键使用字符串时,需要考虑大小写、编码、长度与排序规则。数据库的默认比较规则可能认为两个外观不同的字符串相同。实验使用 VARBINARY 保存操作标识,明确按字节比较,避免隐含的大小写折叠改变测试含义。

真实系统应按业务定义选择比较规则,并在入口规范化后保存原始或可审计信息。截断键、随意转小写或跨租户复用键,都可能让原本不同的操作相互覆盖。

9.4 幂等需要保留期限与可查询结果

唯一记录删除后,旧请求再来可能重新执行。需要说明记录保留多久、客户端重试窗口多久,以及成功结果怎样查询。对外返回“重复请求”只能说明没有新建记录,不足以说明原操作已经成功。

实验验证当前数据库生命周期内的去重,不提供完整的业务 API 或跨系统幂等。下一章会使用同一业务身份,处理提交响应丢失时的未知状态。

练习。 同一租户重复使用同一个键,但数量由 1 改为 2。你会返回什么,依据保存在哪里?如果只保存键而不保存请求指纹,系统还缺少哪项判断证据?