幂等性——网络抖动重复支付的解决方法

目录

问题

"用户手抖,连续点击了两次'支付'按钮,或者网络抖动导致前端重发了请求,你的后端接口怎么保证不扣用户两笔钱?"

先查后写??

首先要明白网络请求到后端一定是高并发多线程的去处理请求的,所以两次的支付请求如果都是先查后改的话会出现下图这样的问题:

1、线程 A 进来,查订单 1001,发现是"未支付"。

2、线程 B 刚好也进来,查订单 1001,发现也是"未支付"。

3、线程 A 执行扣款,把状态改为"已支付"。

4、线程 B 继续执行扣款,再次扣钱,覆盖状态。

这样就会造成扣两次钱的P0级严重错误

解决方法

1、数据库唯一索引

在数据库里建一张"流水表(Payment_Log)",把 order_id 或者 全局唯一流水号 建为 Unique Key(唯一索引)。请求来了,先往流水表插数据。如果插成功了,说明是第一次请求,继续跑业务。如果报了 DuplicateKeyException(主键冲突),说明是重复请求,直接 Catch 异常,返回"支付成功"。

2、Redis Token 机制

第一阶段: 用户进入收银台页面,前端先调后端接口获取一个全局唯一的 Token,存入 Redis。

第二阶段: 用户点"支付"时,把这个 Token 带在 Header 里传给后端。

后端校验: 后端拿到 Token,去 Redis 删掉这个 Key。

如果删除成功(返回 1),说明是第一次,放行。

如果删除失败(返回 0),说明已经用过了,直接拦截。

"必须先删 Token! 这叫'先斩后奏'。 如果你先跑业务,业务跑了一半网络断了,Token 还在,用户重试时就又穿透了。当然,先删 Token 有个小问题:如果业务跑失败了,Token 也没了,用户想重试怎么办?简单的解法是:后端返回特定错误码,前端捕获后自动申请一个新的 Token 再重试。"

3、状态机 + 乐观锁

状态机:在UPDATE时添加状态条件筛选。

乐观锁:UPDATE 语句在数据库内部不能同时修改"同一行数据"。(数据苦行锁)

cpp 复制代码
UPDATE orders SET status = 'PAID' 
WHERE id = 1001 AND status = 'UNPAID'; -- 关键在这里!

利用数据库行锁的原子性。只有当当前状态真的是"未支付"时,这条 SQL 才会执行成功

如果第二个请求过来,发现状态已经是 PAID 了,Where 条件不满足

相关推荐
怣5021 小时前
MySQL多表连接完全指南:内连接与外连接超详细讲解
数据库·sql·mysql
Bruk.Liu1 天前
(LangChain实战12):LangChain中的新型Chain之create_sql_query_chain
数据库·人工智能·sql·langchain
码海踏浪1 天前
从简单到专业在OceanBase中查看SQL是否走索引
数据库·sql·oceanbase
cheems95271 天前
【MySQL】SQL调优:数据库性能剖析
数据库·sql·mysql
A懿轩A1 天前
【MySQL 数据库】SQL 基础语法速成:SELECT / INSERT / UPDATE / DELETE 一篇上手增删改查
数据库·sql·mysql
常利兵1 天前
Spring Boot 3 多数据源整合 Druid:监控页面与控制台 SQL 日志配置实战
android·spring boot·sql
Aloudata1 天前
指标中台选型技术实测:Aloudata CAN 如何通过 NoETL 语义层驾驭复杂 SQL 生成
大数据·数据库·sql·数据分析·指标平台
星沙丘秋1 天前
传统数仓(T+1)中关于异常任务的监控SQL方案
数据库·sql
TTBIGDATA1 天前
【Hue】Ambari开启 Kerberos 后,Hue 使用 Spark SQL出现凭证不统一问题处理
大数据·sql·spark·ambari·kerberos·hue·bigtop
山岚的运维笔记2 天前
SQL Server笔记 -- 第15章:INSERT INTO
java·数据库·笔记·sql·microsoft·sqlserver