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