应优先使用 upsert 而非先查后写实现幂等写入,因其通过唯一约束在数据库层原子性处理“存在则更新、不存在则插入”,避免并发竞态;需配合唯一索引、精准捕获冲突错误码(如 postgresql 23505、mysql 1062),或结合 redis setnx + 请求 id 实现跨服务幂等。

用 upsert 而不是先查后写
绝大多数“幂等写入”需求,本质是避免重复插入相同数据。直接查再判断再插入(SELECT + INSERT)在并发下必然出错——两个请求同时查不到,然后都插入成功。真正可靠的做法是把“存在则更新、不存在则插入”压进一条语句里。
PostgreSQL 用 ON CONFLICT,MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,SQLite 用 INSERT OR REPLACE 或 INSERT OR IGNORE 配合后续 UPDATE。关键前提是:必须有唯一约束(UNIQUE 或 PRIMARY KEY),否则 upsert 无从判断“重复”。
- 别在应用层做
if not exists: insert()—— 这不是幂等,是竞态温床 - 唯一约束字段要选业务上真正能标识“同一行”的列,比如
order_id、user_id+date组合,而不是自增id - MySQL 的
REPLACE INTO会先删后插,触发DELETE和INSERT双重触发器,且自增 ID 可能跳变,优先用ON DUPLICATE KEY UPDATE
try/except 捕获唯一约束冲突最稳妥
ORM(如 SQLAlchemy、Django ORM)或原生驱动不总直接暴露 upsert 接口,尤其跨数据库时。此时更通用的做法是:直接 INSERT,靠数据库抛出的唯一约束错误来兜底。
PostgreSQL 错误码是 23505(unique_violation),MySQL 是 1062(ER_DUP_ENTRY)。捕获后不做任何写操作,即完成幂等。
立即学习“Python免费学习笔记(深入)”;
BJXShop网上购物系统是一个高效、稳定、安全的电子商店销售平台,经过近三年市场的考验,在中国网购系统中属领先水平;完善的订单管理、销售统计系统;网站模版可DIY、亦可导入导出;会员、商品种类和价格均实现无限等级;管理员权限可细分;整合了多种在线支付接口;强有力搜索引擎支持... 程序更新:此版本是伴江行官方商业版程序,已经终止销售,现于免费给大家使用。比其以前的免费版功能增加了:1,整合了论坛
- 不要捕获宽泛的
Exception,只盯住明确的唯一冲突错误码,否则掩盖真实问题 - Django 中可用
django.db.IntegrityError,但需进一步检查args[0]或__cause__.pgcode才能区分是不是真因唯一键冲突 - SQLAlchemy 用
exc.IntegrityError,配合connection.execute(...).rowcount判断是否真的没插入成功(有些驱动异常时rowcount仍为 1)
Redis SETNX + 数据库写入组合防重复提交
当“幂等”边界超出单条 SQL(比如一个下单请求含库存扣减 + 订单生成 + 消息投递),就得靠外部协调。Redis 的 SETNX(set if not exists)是最轻量的分布式锁前置手段。
流程是:用请求 ID(如 request_id 或签名摘要)作为 key,SETNX 成功才执行后续数据库操作;失败则直接返回已处理。注意过期时间必须设(EX 参数),否则锁残留会导致永久阻塞。
-
SETNX后必须跟EXPIRE,推荐合并用SET key value EX 30 NX,避免中间状态丢失 - key 命名要有业务上下文,比如
order:create:<code>sha256(user_id+goods_id+timestamp),别裸用request_id(可能被重放) - 别在 Redis 成功后又去数据库查一遍“是否已存在”——这又回到竞态起点;
SETNX成功即代表首次进入,失败即代表已处理
HTTP 幂等性不能只靠后端,客户端也要配合
后端做再多,如果前端按钮重复点击、网络超时后盲目重试,照样产生多条记录。真正的幂等链路必须两端对齐。
关键动作是:客户端生成唯一请求标识(X-Idempotency-Key header),服务端用它做去重依据(存 Redis 或数据库临时表)。这个 key 必须由客户端生成并保证重试时复用,而不是服务端每次生成新 key。
- 别让前端用时间戳或随机数当
Idempotency-Key——重试时变了就失效;应基于请求内容哈希,或由前端持久化一次后复用 - 服务端收到重复 key 时,不能只返回 200,得返回原始响应体(比如第一次创建成功的订单 JSON),否则前端无法拿到数据
- 该机制对
POST有效,但对GET、PUT、DELETE本身具备幂等性,无需额外加 key
最难的从来不是写个 upsert 或捕获异常,而是厘清“哪一层该承担哪部分幂等责任”:数据库管单行唯一,Redis 管跨服务操作,HTTP 头管端到端请求生命周期。混用或漏掉任意一层,都会在某个并发密度下突然崩掉。









