首页/数据/NoSQL 速查/findOneAndUpdate 原子读改写
MongoDB99 条

findOneAndUpdate 原子读改写

一步完成查找与更新并返回文档,适合领取队列任务、原子计数器等场景;排序决定哪条文档先被选中。

NoSQL 速查数据更新于 2026-10-13

findOneAndUpdate 做什么

findOneAndUpdate 把查找与更新合成一次原子操作,并返回被操作的文档,底层命令是 findAndModify。它常用于领取队列任务、原子计数器、状态机流转这类「读出来同时改掉」的场景,排序决定哪条文档先被选中。

基本信息

常用选项sort、returnDocument(before / after)、upsert、projection
对应命令findAndModify
返回内容默认返回更新前的文档,returnDocument: "after" 返回更新后的
并发保证单文档操作是原子的,并发调用同一条件时每个匹配文档只会被一方取走
旧参数returnOriginal: false 等价于 returnDocument: "after"

典型使用场景

  • 领取任务:把排队中的任务标记为运行中并取回内容
  • 原子自增并返回新值:计数与序列号生成
  • 状态机流转:条件匹配时才更新,避免并发覆盖
  • 库存扣减:条件里带上余额判断,避免超卖
  • 去重入队:不存在才插入并返回结果

常用写法

常用写法
// 领取一条任务:按优先级排序,返回更新后的文档
 db.tasks.findOneAndUpdate(
   { status: "queued" },
   { $set: { status: "running", worker: "w1", startAt: new Date() } },
   { sort: { priority: -1 }, returnDocument: "after" }
 )

// 原子自增并返回新值
 db.counters.findOneAndUpdate(
   { _id: "orderNo" },
   { $inc: { value: 1 } },
   { upsert: true, returnDocument: "after" }
 )

// 条件更新:仍处于支付中才置为已支付,防止并发覆盖
 db.orders.findOneAndUpdate(
   { _id: 1001, status: "paying" },
   { $set: { status: "paid", paidAt: new Date() } },
   { returnDocument: "after" }
 )

选项与行为

选项作用注意
sort决定选中哪条文档不写时结果不确定
returnDocumentbefore 或 after旧参数 returnOriginal 语义相反
upsert无匹配时插入新文档需与唯一索引配合才并发安全
projection限定返回字段只影响返回值,不影响更新内容
旧参数 returnOriginal: false 等价于 returnDocument: "after";并发场景下每个匹配文档只会被一个调用方取走,这正是单文档原子操作带来的语义。
返回的是执行那一刻的快照,后续判断要用这个返回值或重新查询,别把它和稍后再查出来的数据混用,否则会基于过期状态做决策。
findOneAndUpdate 与 updateOne 的差别就在返回值:前者一次往返就把文档拿到手,适合读改写;后者只返回匹配数与修改数,想拿到内容还得再查一次,两步之间就留出了竞态窗口。
领取任务靠的是条件更新本身:把 status 从 queued 改成 running,并发下同一条只会被一方取走;任务处理完再写回结果,中途失败要有一套超时回收把 running 放回队列,否则任务会永久卡住。

易错点

  • 不写 sort 时选中哪条文档不确定,领取任务类场景必须显式排序,否则任务分配顺序不可控
  • 把返回的文档当成当前最新状态使用:它是执行时的快照,后续再查一次可能已经被其他写入改动
  • 混用 returnOriginal 与 returnDocument 会让返回语义相反,改造代码时要一并核对这两处写法
  • 查询条件里完全不设状态约束,只靠读到的结果判断,并发下会出现同一任务被重复处理

数据来源与参考

  • MongoDB Manual: db.collection.findOneAndUpdate()
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌