数据库唯一索引最可靠,因其是db层最强约束,直接拦截重复插入并抛出duplicate entry错误,不依赖外部服务,避免应用层查-插竞态及redis故障导致的误判。

为什么直接用数据库唯一索引做幂等最可靠
因为业务逻辑再怎么校验,最终落地时都绕不开数据一致性。唯一索引是数据库层最硬的约束,只要 INSERT 报 ERROR 1062: Duplicate entry,就说明请求已存在,直接返回成功或忽略即可。
常见错误是只在应用层查一遍再插入,但并发下查-插之间存在竞态窗口;也有人用 Redis SETNX 做前置锁,但网络分区或 Redis 故障会导致误判——而唯一索引由 DB 强保证,不依赖外部服务。
- 建表时给业务关键字段加
UNIQUE KEY,比如order_id、pay_no或组合键(user_id, action_type, trace_id) - Go 中捕获 MySQL 错误码:用
errors.Is(err, mysql.ErrDupKey)或正则匹配"Duplicate entry"(不同驱动略有差异) - 不要把唯一冲突当作异常处理,它本质是“正常业务路径”——返回 200 + 已存在结果,而非 500
如何用 Redis + Lua 做短时效幂等(适用于非核心写操作)
当业务不能改表结构,或需要拦截高频重复请求(如秒杀预占、短信发送),Redis 是折中选择。但必须用 Lua 脚本原子执行 SET + 过期,否则 GET + SET 有竞态。
典型陷阱是只设 key 不设过期时间,导致缓存永久堆积;或用 EXPIRE 分两步调用,中间可能被其他请求覆盖。
- 使用
redis.Eval(ctx, "return redis.call('set', KEYS[1], '1', 'NX', 'EX', ARGV[1])", []string{key}, ttlSeconds) -
key应包含足够业务上下文,比如"idempotent:" + userId + ":" + orderId + ":" + timestamp,避免跨用户冲突 - 注意 Lua 返回值:成功返回
1,失败(已存在)返回nil,Go 里要判空而非判 err
HTTP 层怎么透传和校验幂等 Key
客户端必须提供稳定、可复现的幂等标识,服务端才能复用。不能依赖随机 UUID 或时间戳——重试时它们会变。
常见错误是前端每次重试都生成新 X-Idempotency-Key,或后端没校验 header 是否缺失就直接往下走。
- 推荐客户端在首次请求时生成一次
uuidv4,并在所有重试中复用该 header - Go HTTP handler 中提前提取:
idempotencyKey := r.Header.Get("X-Idempotency-Key"),若为空直接返回400 Bad Request - 不要把幂等 key 存进 context 并一路透传到 DAO 层——容易遗漏;应在 handler 或 middleware 就完成存储/校验动作
事务内怎么安全回滚幂等记录
如果幂等检查通过后,后续业务逻辑出错需要回滚,而幂等记录(如 Redis key 或临时表行)又不能跟着一起回滚,就会导致“假成功”。这时候必须明确:幂等标识本身代表“该请求已被接受”,哪怕业务失败也不应重复处理。
所以幂等记录必须在事务外写入,且设计为“只增不删”——这是幂等性的前提,不是 bug。
- 例如用
INSERT IGNORE写幂等表,失败不报错;成功后才开启业务事务 - 若用 Redis,写入后即使后续 DB 回滚,该 key 仍有效,下次重试直接返回上一次结果(需确保结果可缓存)
- 特别注意分布式事务场景:不要试图用 Saga 撤销幂等操作,它本就不该被撤销
真正难的是结果一致性——比如支付回调幂等后返回成功,但下游账户没扣款。这种得靠对账补偿,不是幂等本身能解决的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











