定义
findAndModify更新并返回单个文档。默认情况下,返回的文档不包含更新所做的修改。要返回修改后的文档,请使用
new选项。提示
In
mongosh, this command can also be run through thedb.collection.findAndModify()helper method.Helper methods are convenient for
mongoshusers, but they may not return the same level of information as database commands. In cases where the convenience is not needed or the additional return fields are required, use the database command.
兼容性
此命令可用于以下环境中托管的部署:
- MongoDB Atlas:用于云中 MongoDB 部署的完全托管服务
注意
所有 MongoDB Atlas 集群都支持此命令。有关 Atlas 对所有命令的支持的信息,请参阅不支持的命令。
MongoDB Enterprise:基于订阅、自我管理的 MongoDB 版本
MongoDB Community:源代码可用、免费使用且可自行管理的 MongoDB 版本
在版本5.0中进行了更改。
语法
该命令具有以下语法:
db.runCommand( { findAndModify: <collection-name>, query: <document>, sort: <document>, remove: <boolean>, update: <document or aggregation pipeline>, new: <boolean>, fields: <document>, upsert: <boolean>, bypassDocumentValidation: <boolean>, writeConcern: <document>, maxTimeMS: <integer>, collation: <document>, arrayFilters: <array>, hint: <document|string>, comment: <any>, let: <document> // Added in MongoDB 5.0 } )
命令字段
该命令接受以下字段:
字段 | 类型 | 说明 | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 文档 | 可选。修改的选择条件。 如果未指定,则默认为空文档。 如果查询参数不是文档,则操作出错。 | ||||||||||||||||||
| 文档 | Optional. Determines which document the operation updates if the query selects multiple documents. 如果 sort 参数不是文档,则操作错误。 MongoDB 不按特定顺序将文档存储在集合中。对包含重复值的字段进行排序时,可能会以任何顺序返回包含这些值的文档。
如果排序条件中指定的字段在两个文档中都不存在,那么它们排序所依据的值是相同的。这两个文档可以以任何顺序返回。 如果需要一致的排序顺序,请在排序中至少纳入一个包含唯一值的字段。最简单方法是在排序查询中纳入 有关更多信息,请参阅排序一致性。 | ||||||||||||||||||
| 布尔 | 必须指定 | ||||||||||||||||||
| 文档或数组 | 必须指定
| ||||||||||||||||||
| 布尔 | 可选。 当 | ||||||||||||||||||
| 文档 | 可选。要返回的字段的子集。 如果字段参数不是文档,则操作错误。 | ||||||||||||||||||
| 布尔 | 可选。与 当
为避免出现多个 upsert 操作,请确保 默认值为 | ||||||||||||||||||
| 布尔 | Optional. Enables | ||||||||||||||||||
| 文档 | |||||||||||||||||||
| non-negative integer | 可选。 指定时间限制(以毫秒为单位)。如果您未指定 MongoDB 使用与 | ||||||||||||||||||
| 字符串 | 要针对其运行该命令的集合。 | ||||||||||||||||||
| 文档 | 可选。 可选。指定用于操作的排序规则。 排序规则允许用户为字符串比较指定特定于语言的规则,例如字母大小写和重音符号规则。 排序规则选项的语法如下: 指定排序规则时, 如果未指定排序规则,但集合具有默认排序规则(请参阅 如果没有为收集或操作指定排序规则,MongoDB 将使用先前版本中使用的简单二进制比较来进行字符串比较。 您不能为一个操作指定多个排序规则。例如,您不能为每个字段指定不同的排序规则,或者如果执行带排序的查找,则不能使用一种排序规则进行查找而另一种排序规则进行排序。 | ||||||||||||||||||
| 阵列 | 选修的。大量过滤器文档,用于确定针对大量字段的更新操作要修改哪些大量元素。 在更新文档中,使用
您可以在更新文档中多次包含相同的标识符;但对于更新文档中的每个不同标识符 ( 但是,您可以在单个过滤器文档中的同一标识符上指定复合条件,例如以下示例: For examples, see Array Update Operations with
| ||||||||||||||||||
| 文档或字符串 | |||||||||||||||||||
| any | 可选。用户提供的待附加到该命令的注释。设置后,该注释将与该命令的记录一起出现在以下位置:
注释可以是任何有效的 BSON 类型(字符串、整型、对象、数组等)。 | ||||||||||||||||||
文档 |
输出
findAndModify命令返回包含以下字段的文档:
字段 | 类型 | 说明 |
|---|---|---|
| 文档 | 包含命令的返回值。有关详细信息,请参阅 |
| 文档 | 包含有关更新文档的信息。有关详细信息,请参阅 |
| 数字 | 包含此命令的执行状态。成功时为 |
lastErrorObject
lastErrorObject 嵌入式文档包含以下字段:
字段 | 类型 | 说明 |
|---|---|---|
| 整型 | 包含与更新谓词匹配的文档数或命令插入或删除的文档数。 |
| 布尔 | 如果为
|
| 文档 | 如果符合 |
value
对于 remove 操作,如果此查询与某一文档匹配,value 则包含已删除的文档。如果此查询与要删除的文档不匹配,value 则包含 null。
对于 update 操作,value 嵌入式文档包含以下内容:
如果未设置
new参数或为false:如果查询结果与文档匹配,则返回修改前的文档;
否则,返回
null。
如果
new为true:如果查询返回匹配项,则返回更新后的文档;
如果设置了
upsert: true且没有与查询匹配的文档,则返回插入的文档;否则,返回
null。
行为
使用唯一索引进行 upsert
除非有唯一索引来防止重复,否则 upsert 可能会创建重复的文档。
以某一情况为例,其中不存在名为 Andy 的文档,且多个客户端大致会在同一时间发出以下命令:
db.runCommand( { findAndModify: "people", query: { name: "Andy" }, update: { $inc: { score: 1 } }, upsert: true } )
If all findAndModify operations finish the query phase before any client successfully inserts data, and there is no unique index on the name field, each findAndModify operation may result in an insert, creating multiple documents with name: Andy.
A unique index on the name field ensures that only one document is created. With a unique index in place, the multiple findAndModify operations now exhibit the following behavior:
Exactly one
findAndModifyoperation will successfully insert a new document.Other
findAndModifyoperations either update the newly-inserted document or fail due to a unique key collision.In order for other
findAndModifyoperations to update the newly-inserted document, all of the following conditions must be met:目标collection具有会导致重复键错误的唯一索引。
更新操作不是
updateMany或multi是false。更新匹配条件为:
单个相等谓词。例如
{ "fieldA" : "valueA" }等式谓词的逻辑 AND。例如
{ "fieldA" : "valueA", "fieldB" : "valueB" }
相等谓词中的字段与唯一索引键模式中的字段匹配。
更新操作不会修改唯一索引键模式中的任何字段。
下表显示了upsert操作的示例,当发生键冲突时,这些操作要么导致更新,要么失败。
唯一索引键模式 | 更新操作 | 结果 | ||||||
|---|---|---|---|---|---|---|---|---|
| | 匹配文档的 | ||||||
| | 操作失败是因为它修改了唯一索引键模式 ( | ||||||
| | 操作失败,因为等值谓词字段( |
分片集合
To use findAndModify on a sharded collection:
如果仅针对一个分片,则可以在
query字段中使用部分分片键,或者,可以在
query字段中提供完整分片键的相等条件。从版本 7.1 开始,您不需要在查询规范中提供分片键 或
_id字段。
分片集合中的文档可能缺少分片键字段。要定位缺失分片键的文档,可将 null 等值匹配与其他过滤条件(例如针对 _id 字段)结合使用。例如:
{ _id: <value>, <shardkeyfield>: null } // _id of the document missing shard key
分片键修改
您可以更新文档的分片键值,除非分片键字段是不可变的 _id 字段。
警告
分片集合中的文档可能缺少分片键字段。采取预防措施,避免在更改文档的分片键值时意外删除分片键。
您必须在
mongos上运行。请勿直接在分片上运行操作。必须在完整分片键上包含相等筛选器。
缺少分片键
分片的集合中的文档可能缺少分片键字段。要使用findAndModify 设立文档缺失的分片键:
您必须在
mongos上运行。请勿直接在分片上运行操作。必须在完整分片键上包含相等筛选器。
提示
由于缺失的键值是作为 null 相等匹配的一部分返回的,因此为避免更新空值键,请酌情纳入其他查询条件(例如 _id 字段)。
另请参阅:
模式验证
The findAndModify command adds support for the bypassDocumentValidation option, which lets you bypass schema validation when inserting or updating documents in a collection with validation rules.
与 update 方法的比较
更新文档时,findAndModify 和updateOne() 方法的运行方式不同:
如果多个文档符合更新条件,则对于
findAndModify,您可以指定sort以对更新哪个文档提供某种控制措施。updateOne()更新第一个匹配的文档。默认下,
findAndModify返回一个对象,其中包含文档修改前的版本以及操作的状态。要获取更新后的文档,请使用new选项。updateOne()方法返回一个包含操作状态的WriteResult()对象。要返回更新后的文档,请使用
find()方法。但是,在更新和文档检索之间,其他更新可能已修改了文档。此外,如果更新只修改了一个文档,但有多个文档匹配,则需要使用额外的逻辑来识别已更新的文档。
修改单个文档时, 和findAndModify updateOne()方法都会自动更新文档。有关这些方法的交互和操作顺序的更多详细信息,请参阅原子性和事务。
事务
findAndModify can be used inside distributed transactions.
重要
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项。
在 ACID 事务中进行更新或插入 (upsert)
如果分布式事务不是跨分片写入事务,则可以在该事务中创建集合和索引。
findAndModify with upsert: true can be run on an existing collection or a non-existing collection. If run on a non-existing collection, the operation creates the collection.
写关注和事务
如果是在事务中运行,则请勿显式设置此操作的写关注。要将写关注与事务一起使用,请参阅事务和写关注。
写关注错误
如果 findAndModify 操作由于未实现写关注(write concern)失败,则该操作将返回 writeConcernError 文档。
示例
更新和返回
以下命令会更新 people 集合中符合 query 条件的现有文档:
db.runCommand( { findAndModify: "people", query: { name: "Tom", state: "active", rating: { $gt: 10 } }, sort: { rating: 1 }, update: { $inc: { score: 1 } } } )
此命令会执行以下操作:
query在people集合中找到一个文档,其中name字段的值为Tom,state字段的值为active,rating字段的值为greater than10。sort会对查询结果进行升序排序。如果多个文档匹配query条件,则该命令会修改排序中的第一个文档。update会将score字段的值increments1。此命令将返回一个包含以下字段的文档:
包含此命令详细信息的
lastErrorObject字段,其中包括为true的updatedExisting字段,以及包含为此更新选择的原始未修改文档的
value字段:{ "lastErrorObject" : { "connectionId" : 1, "updatedExisting" : true, "n" : 1, "syncMillis" : 0, "writtenTo" : null, "err" : null, "ok" : 1 }, value" : { "_id" : ObjectId("54f62d2885e4be1f982b9c9c"), "name" : "Tom", "state" : "active", "rating" : 100, "score" : 5 }, "ok" : 1 }
要在value字段中返回更新后的文档,请在命令中添加new:true选项。
如果没有文档与 query 条件匹配,此命令则会返回一个在 value 字段中包含 null 的文档:
{ "value" : null, "ok" : 1 }
mongosh,许多驱动程序都提供 辅助方法。使用Shell findAndModify()助手,前一操作可以采用以下形式:
db.people.findAndModify( { query: { name: "Tom", state: "active", rating: { $gt: 10 } }, sort: { rating: 1 }, update: { $inc: { score: 1 } } } );
但是, findAndModify() Shell 助手方法仅返回未修改的文档,或者,如果new为true ,则返回更新的文档。
{ "_id" : ObjectId("54f62d2885e4be1f982b9c9c"), "name" : "Tom", "state" : "active", "rating" : 100, "score" : 5 }
upsert: true
以下命令使用 upsert: true 更新匹配文档,如果不存在匹配,则插入新文档:
db.runCommand( { findAndModify: "people", query: { name: "Gus", state: "active", rating: 100 }, sort: { rating: 1 }, update: { $inc: { score: 1 } }, upsert: true } )
如果此命令找到匹配的文档,则会执行更新。
如果此命令未找到匹配的文档,则符合 upsert: true 的 update 操作则会触发插入并返回包含以下字段的文档:
包含此命令详细信息的
lastErrorObject字段,其中包括包含新插入文档的_id值的字段upserted,以及包含
null的value字段。
{ "value" : null, "lastErrorObject" : { "updatedExisting" : false, "n" : 1, "upserted" : ObjectId("54f62c8bc85d4472eadea26f") }, "ok" : 1 }
returnNewDocument
以下命令使用 upsert: true 和 new: true 来更新匹配文档或插入新文档,并在 value 字段中返回新文档或更新文档。
在以下示例中,people 集合中没有文档与 query 条件匹配:
db.runCommand( { findAndModify: "people", query: { name: "Pascal", state: "active", rating: 25 }, sort: { rating: 1 }, update: { $inc: { score: 1 } }, upsert: true, new: true } )
此命令将在 value 字段中返回新插入的文档:
{ "lastErrorObject" : { "connectionId" : 1, "updatedExisting" : false, "upserted" : ObjectId("54f62bbfc85d4472eadea26d"), "n" : 1, "syncMillis" : 0, "writtenTo" : null, "err" : null, "ok" : 1 }, "value" : { "_id" : ObjectId("54f62bbfc85d4472eadea26d"), "name" : "Pascal", "rating" : 25, "state" : "active", "score" : 1 }, "ok" : 1 }
排序和删除
通过在 rating 字段中包含 sort 规范,以下示例从 people 集合中删除 state 值为 active 且具有匹配文档中最低rating 的单个文档:
db.runCommand( { findAndModify: "people", query: { state: "active" }, sort: { rating: 1 }, remove: true } )
此命令将返回已删除的文档:
{ "lastErrorObject" : { "connectionId" : 1, "n" : 1, "syncMillis" : 0, "writtenTo" : null, "err" : null, "ok" : 1 }, "value" : { "_id" : ObjectId("54f62a6785e4be1f982b9c9b"), "name" : "XYZ123", "score" : 1, "state" : "active", "rating" : 3 }, "ok" : 1 }
指定排序规则。
排序规则允许用户为字符串比较指定特定于语言的规则,例如字母大小写和重音符号规则。
集合 myColl 包含以下文档:
{ _id: 1, category: "café", status: "A" } { _id: 2, category: "cafe", status: "a" } { _id: 3, category: "cafE", status: "a" }
以下操作包括排序规则选项:
db.runCommand( { findAndModify: "myColl", query: { category: "cafe", status: "a" }, sort: { category: 1 }, update: { $set: { status: "Updated" } }, collation: { locale: "fr", strength: 1 } } )
该操作将返回以下文档:
{ "lastErrorObject" : { "updatedExisting" : true, "n" : 1 }, "value" : { "_id" : 1, "category" : "café", "status" : "A" }, "ok" : 1 }
数组更新操作 arrayFilters
注意
arrayFilters 不适用于使用聚合管道的更新。
更新数组字段时,您可以指定 arrayFilters 确定要更新哪些数组元素。
更新元素匹配 arrayFilters 条件
注意
arrayFilters 不适用于使用聚合管道的更新。
使用以下文档创建集合 students:
db.students.insertMany( [ { "_id" : 1, "grades" : [ 95, 92, 90 ] }, { "_id" : 2, "grades" : [ 98, 100, 102 ] }, { "_id" : 3, "grades" : [ 95, 110, 100 ] } ] )
要更新grades数组中所有大于或等于100的元素,请使用带有arrayFilters选项的位置$[<identifier>]操作符:
db.runCommand( { findAndModify: "students", query: { grades: { $gte: 100 } }, update: { $set: { "grades.$[element]" : 100 } }, arrayFilters: [ { "element": { $gte: 100 } } ] } )
该操作更新单个文档的 grades 字段,完成该操作后,集合中将包含以下文档:
{ "_id" : 1, "grades" : [ 95, 92, 90 ] } { "_id" : 2, "grades" : [ 98, 100, 100 ] } { "_id" : 3, "grades" : [ 95, 110, 100 ] }
更新文档数组的特定元素
注意
arrayFilters 不适用于使用聚合管道的更新。
使用以下文档创建集合 students2:
db.students2.insertMany( [ { "_id" : 1, "grades" : [ { "grade" : 80, "mean" : 75, "std" : 6 }, { "grade" : 85, "mean" : 90, "std" : 4 }, { "grade" : 85, "mean" : 85, "std" : 6 } ] }, { "_id" : 2, "grades" : [ { "grade" : 90, "mean" : 75, "std" : 6 }, { "grade" : 87, "mean" : 90, "std" : 3 }, { "grade" : 85, "mean" : 85, "std" : 4 } ] } ] )
以下操作查找_id字段等于1的文档,并使用筛选后的位置运算符$[<identifier>]和arrayFilters来更新grades数组中分数大于以下值的所有元素的mean :小于或等于85 。
db.runCommand( { findAndModify: "students2", query: { _id : 1 }, update: { $set: { "grades.$[elem].mean" : 100 } }, arrayFilters: [ { "elem.grade": { $gte: 85 } } ] } )
该操作更新单个文档的 grades 字段,完成该操作后,集合中将包含以下文档:
{ "_id" : 1, "grades" : [ { "grade" : 80, "mean" : 75, "std" : 6 }, { "grade" : 85, "mean" : 100, "std" : 4 }, { "grade" : 85, "mean" : 100, "std" : 6 } ] } { "_id" : 2, "grades" : [ { "grade" : 90, "mean" : 75, "std" : 6 }, { "grade" : 87, "mean" : 90, "std" : 3 }, { "grade" : 85, "mean" : 85, "std" : 4 } ] }
使用聚合管道进行更新
findAndModify 可以接受聚合管道以用于更新。该管道可以由以下阶段组成:
$addFields及其别名$set$replaceRoot及其别名$replaceWith
使用聚合管道可以进行更具表现力的更新声明,例如基于当前字段值的表达条件更新或使用另一个字段的值更新一个字段。
例如,使用以下文档创建名为 students2 的集合:
db.students2.insertMany( [ { "_id" : 1, "grades" : [ { "grade" : 80, "mean" : 75, "std" : 6 }, { "grade" : 85, "mean" : 90, "std" : 4 }, { "grade" : 85, "mean" : 85, "std" : 6 } ] }, { "_id" : 2, "grades" : [ { "grade" : 90, "mean" : 75, "std" : 6 }, { "grade" : 87, "mean" : 90, "std" : 3 }, { "grade" : 85, "mean" : 85, "std" : 4 } ] } ] )
以下操作查找 _id 字段等于 1 的文档,并使用聚合管道根据 grades 字段计算新字段 total 的值:
db.runCommand( { findAndModify: "students2", query: { "_id" : 1 }, update: [ { $set: { "total" : { $sum: "$grades.grade" } } } ], new: true } )
操作完成后,集合有以下文档:
{ "_id" : 1, "grades" : [ { "grade" : 80, "mean" : 75, "std" : 6 }, { "grade" : 85, "mean" : 90, "std" : 4 }, { "grade" : 85, "mean" :85, "std" : 6 } ], "total" : 250 } { "_id" : 2, "grades" : [ { "grade" : 90, "mean" : 75, "std" : 6 }, { "grade" : 87, "mean" : 90, "std" : 3 }, { "grade" : 85, "mean" : 85,"std" : 4 } ] }
为 findAndModify 操作指定 hint
在mongosh members中,使用以下文档创建 集合:
db.members.insertMany( [ { "_id" : 1, "member" : "abc123", "status" : "P", "points" : 0, "misc1" : null, "misc2" : null }, { "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment" }, { "_id" : 3, "member" : "lmn123", "status" : "P", "points" : 0, "misc1" : null, "misc2" : null }, { "_id" : 4, "member" : "pqr123", "status" : "D", "points" : 20, "misc1" : "Deactivated", "misc2" : null }, { "_id" : 5, "member" : "ijk123", "status" : "P", "points" : 0, "misc1" : null, "misc2" : null }, { "_id" : 6, "member" : "cde123", "status" : "A", "points" : 86, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment" } ] )
在集合上创建以下索引:
db.members.createIndex( { status: 1 } ) db.members.createIndex( { points: 1 } )
以下操作会显式提示使用索引 { status: 1 }:
db.runCommand({ findAndModify: "members", query: { "points": { $lte: 20 }, "status": "P" }, remove: true, hint: { status: 1 } })
注意
如果指定不存在的索引,则操作出错。
要查看使用的索引,请对操作运行 explain:
db.runCommand( { explain: { findAndModify: "members", query: { "points": { $lte: 20 }, "status": "P" }, remove: true, hint: { status: 1 } }, verbosity: "queryPlanner" } )
使用以下项中的变量: let
版本 5.0 中的新增功能。
要定义可在命令中其他位置访问的变量,请使用 let 选项。
注意
要使用变量筛选结果,您必须在 $expr 操作符中访问该变量。
创建集合 cakeFlavors:
db.cakeFlavors.insertMany( [ { _id: 1, flavor: "chocolate" }, { _id: 2, flavor: "strawberry" }, { _id: 3, flavor: "cherry" } ] )
以下示例在 let 中定义了 targetFlavor 变量,并使用这些变量将蛋糕口味从樱桃更改为橙子:
db.cakeFlavors.runCommand( { findAndModify: db.cakeFlavors.getName(), query: { $expr: { $eq: [ "$flavor", "$$targetFlavor" ] } }, update: { flavor: "orange" }, let: { targetFlavor: "cherry" } } )