Python 幂等写入的多种实现方式

4次阅读

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

Python 幂等写入的多种实现方式

upsert 而不是先查后写

绝大多数“幂等写入”需求,本质是避免重复插入相同数据。直接查再判断再插入(select + INSERT)在并发下必然出错——两个请求同时查不到,然后都插入成功。真正可靠的做法是把“存在则更新、不存在则插入”压进一条语句里。

PostgreSQL 用 ON CONFLICT,MySQL 用 INSERT ... ON DUPLICATE KEY UPdatesqliteINSERT OR REPLACEINSERT OR IGNORE 配合后续 UPDATE。关键前提是:必须有唯一约束(UNIQUEPRIMARY KEY),否则 upsert 无从判断“重复”。

  • 别在应用层做 if not exists: insert() —— 这不是幂等,是竞态温床
  • 唯一约束字段要选业务上真正能标识“同一行”的列,比如 order_iduser_id+date 组合,而不是自增 id
  • MySQL 的 REPLACE INTO 会先删后插,触发 deleteINSERT 双重触发器,且自增 ID 可能跳变,优先用 ON DUPLICATE KEY UPDATE

try/except 捕获唯一约束冲突最稳妥

ORM(如 SQLAlchemy、django ORM)或原生驱动不总直接暴露 upsert 接口,尤其跨数据库时。此时更通用的做法是:直接 INSERT,靠数据库抛出的唯一约束错误来兜底。

PostgreSQL 错误码是 23505unique_violation),MySQL 是 1062ER_DUP_ENTRY)。捕获后不做任何写操作,即完成幂等。

立即学习Python免费学习笔记(深入)”;

  • 不要捕获宽泛的 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 有效,但对 GETPUTDELETE 本身具备幂等性,无需额外加 key

最难的从来不是写个 upsert 或捕获异常,而是厘清“哪一层该承担哪部分幂等责任”:数据库管单行唯一,Redis 管跨服务操作,HTTP 头管端到端请求生命周期。混用或漏掉任意一层,都会在某个并发密度下突然崩掉。

text=ZqhQzanResources