mongodb事务重试仍写重复数据,因其不识别重复请求,仅保证原子性与回滚;需依赖唯一索引+upsert实现单次操作级幂等,而非事务重试机制。

事务重试时为什么还会写重复数据
MongoDB 事务本身不识别“这是上次那个没收到响应的请求”,只保证原子性和回滚能力。网络超时、客户端崩溃或驱动自动重试,都可能导致同一逻辑被多次提交——服务端看到的是新 session 或未持久化的 transactionNumber,就当作全新事务执行。尤其在 writeConcern: { w: "majority" } 下,主节点已提交但响应丢失,客户端重发,副本集就会执行两次。
必须用 upsert + 唯一索引,而不是 insertOne
insertOne() 在重复提交时直接报 E11000 duplicate key 错误,但业务层得自己捕获并忽略;而 updateOne() 配合 upsert: true 是原子级“有则更新、无则插入”,天然幂等。关键点:
- 在业务唯一字段(如
order_id、payment_no)上建唯一索引:db.orders.createIndex({ order_id: 1 }, { unique: true }) - 所有写入统一走
updateOne({ order_id: "ORD-2026-001" }, { $set: { ... } }, { upsert: true }) - 避免在事务里先
find()再insertOne()—— 这会引入竞态窗口,且事务内查不到自己未提交的插入
事务内做幂等检查要复用 session 并加前置校验
如果业务强依赖事务上下文(比如扣库存 + 生单 + 记流水三者必须原子),又需容忍重试,就得靠客户端和驱动协同:
- 手动管理
ClientSession,每次重试都复用同一个 session 对象(不能新建) - 事务开始前,用该 session 执行一次轻量
findOne({ order_id: "..." })检查是否已存在,存在则直接跳过整个事务体 - 事务体内部仍要用
upsert,双重保险——session 复用防跨请求重试,upsert防同 session 内异常分支重复执行
别把 Redis 锁当事务幂等的银弹
Redis 做请求去重在 HTTP 接口层有效,但嵌入 MongoDB 事务时反而容易出问题:
- Redis 操作和 MongoDB 事务不在同一原子域:Redis 成功但事务失败 → 锁残留,后续请求被误拒
- Redis 失联或超时,锁没设上 → 事务照常执行,重复写入
- Redis 集群模式下
SET NX EX在 slot 迁移期间有边界 case
真正要保事务级幂等,唯一可靠锚点只有 MongoDB 自身的唯一索引 + upsert 语义,其他都是辅助手段。











