接口幂等怎么保证?
更新时间:2026-09-18。问题驱动短答页。幂等 = 执行一次和执行 N 次效果相同。网络重试、用户连点、MQ 重投都会让同一请求到达多次。
一句话回答
先分清:GET/PUT/DELETE 语义上天然幂等,需要设计的是写(尤其下单、支付、扣款)接口。按场景选方案——防重复提交用 token 机制,写库去重用 唯一索引/去重表,订单流转用状态机条件更新,并发更新同一行用乐观锁版本号,跨资源串行用分布式锁兜底。
五种方案
| 方案 | 做法 | 适用 |
|---|---|---|
| token 机制 | 进页面先领 token,提交带上,服务端校验并一次性删除 | 表单/按钮防重复提交 |
| 唯一索引/去重表 | 业务唯一键(订单号)建唯一约束,重复插入冲突 | 创建类接口、MQ 消费 |
| 状态机 | UPDATE ... WHERE status=前置态,看影响行数 | 订单/支付流转 |
| 乐观锁版本号 | UPDATE ... SET version=version+1 WHERE id=? AND version=? | 并发更新同一记录 |
| 分布式锁 | 以业务 ID 加锁串行化 | 多步操作、跨库,慎用 |
token 防重提交流程

关键:校验+删除必须是原子操作(Lua 脚本或 DEL 返回值判断),不能「先查存在再删」,否则并发两次都查到。
写库幂等:唯一约束 + 同事务
sql
-- 订单号唯一索引;重复请求第二次插入直接冲突,捕获后返回首次结果
INSERT INTO t_order(order_no, ...) VALUES (?,...);
-- 或状态机:只有前置态能转
UPDATE t_order SET status='PAID' WHERE order_no=? AND status='UNPAID';选型建议
- 前端按钮置灰/loading 只是体验优化,能挡误触,挡不住重放和并发,服务端必须独立保证。
- 支付/下单最稳的组合是「客户端幂等号 + 服务端唯一约束 + 状态机」:重试时客户端复用同一个幂等号,服务端识别到重复直接返回首次处理结果(而不是报错)。
- 涉及多个资源、无法用唯一键表达时,才用 Redis 分布式锁 串行化,但锁本身要设过期时间和 owner 校验,且它是兜底而非首选。
坑
- 每次重试都生成新幂等号:服务端无法识别为同一请求,去重失效,重试必须复用同一 id。
- 去重表插入和业务更新不在同一事务:标记成功、业务失败会导致后续正确请求被误判重复而漏处理。
- 只做前端防连点:抓包重放、网关自动重试照样重复下单。
- 唯一冲突后直接抛 500:应识别重复请求并返回首次结果,保证重试方拿到一致响应。
- 用悲观锁/分布式锁包住大事务:锁等待放大延迟,能用唯一索引/状态机就别上锁。
完整 token/唯一键/状态机设计见 接口幂等设计:token/唯一键/状态机;MQ 侧幂等见 消息重复消费怎么解决。
总结
保证接口幂等先认清 GET/PUT/DELETE 天然幂等、真正要设计的是下单支付类写接口:表单防重复提交用「领取-原子消费」的 token 机制,创建类与消息消费用业务唯一索引或同事务去重表,订单支付流转用带前置状态条件的 UPDATE 看影响行数,并发更新用版本号乐观锁,跨资源复杂操作才以带 owner 和过期时间的分布式锁兜底;务必让客户端重试复用同一幂等号、服务端对重复请求返回首次结果、去重标记与业务同事务,且不要指望前端置灰能替代服务端幂等。