findAndModify
定义
findAndModify
的
findAndModify
命令更新并返回单个文档。默认情况下,返回的文档不包含所做的更新修改。要返回包含所做的更新修改的文档,请使用new
选项。提示
在
mongosh
中,此命令也可以通过db.collection.findAndModify()
辅助方法.辅助方法对
mongosh
用户来说很方便,但它们可能不会返回与数据库命令相同级别的信息。在不需要方便或需要额外的返回字段的情况下,请使用数据库命令。
兼容性
此命令在以下环境中托管的服务器可用
MongoDB Atlas:云中 MongoDB 部署的完全托管服务
注意
此命令在所有 MongoDB Atlas 集群中都受支持。有关 Atlas 对所有命令的支持信息,请参阅 不受支持的命令。
MongoDB企业版:基于订阅、自助管理的MongoDB版本
MongoDB社区版:开源、免费使用、自助管理的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 } )
命令字段
该命令包含以下字段
字段 | 类型 | 描述 | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
query | 文档 | 可选。修改的选择标准。该 如果未指定,则默认为空文档。 如果查询参数不是文档,则操作错误。 | ||||||||||||||||||
| 文档 | 可选。确定如果查询选择多个文档,则操作更新的文档。 如果排序参数不是文档,则操作错误。 MongoDB不会按特定顺序存储集合中的文档。当对一个包含重复值的字段进行排序时,包含这些值的文档可能以任何顺序返回。 如果需要一致的排序顺序,请在排序中包含至少一个包含唯一值的字段。最简单的方法是在排序查询中包含 有关更多信息,请参阅 排序一致性。 | ||||||||||||||||||
删除 | 布尔值 | 必须指定 remove 或 update 字段。删除 query 字段中指定的文档。将其设置为 true 以删除选定的文档。默认为 false 。 | ||||||||||||||||||
更新 | 文档或数组 | 必须指定
| ||||||||||||||||||
新 | 布尔值 | 可选。当设置为 true 时,返回更新后的文档而不是原始文档。默认为 false 。 | ||||||||||||||||||
字段 | 文档 | 可选。要返回的字段子集。该 如果字段参数不是一个文档,则操作会出错。 | ||||||||||||||||||
upsert | 布尔值 | 可选。与 当设置为
为了避免多个 upsert,请确保 默认为 | ||||||||||||||||||
bypassDocumentValidation | 布尔值 | 可选。启用 findAndModify 在操作期间绕过文档验证。这允许您更新不符合验证要求的文档。 | ||||||||||||||||||
writeConcern | 文档 | |||||||||||||||||||
maxTimeMS | 非负整数 | 可选。 指定一个以毫秒为单位的时间限制。如果您未指定 MongoDB使用与 | ||||||||||||||||||
findAndModify | 字符串 | 要运行命令的集合。 | ||||||||||||||||||
collation | 文档 | 可选。 指定操作要使用的排序规则。 排序规则允许用户指定字符串比较的语言特定规则,例如字母大小写和重音符号的规则。 排序规则的语法如下
指定排序规则时, 如果未指定排序规则,但集合具有默认排序规则(请参阅 如果为集合或操作未指定排序规则,MongoDB将使用先前版本中用于字符串比较的简单二进制比较。 您不能为操作指定多个排序规则。例如,您不能按字段指定不同的排序规则,或者在进行带有排序的查找时,您不能为查找和排序使用不同的排序规则。 | ||||||||||||||||||
arrayFilters | 数组 | 可选。一个过滤器文档数组,用于确定更新操作中要修改的数组字段的哪些数组元素。 在更新文档中,使用
在更新文档中,您可以多次包含相同的标识符;然而,对于更新文档中每个不同的标识符(
但是,您可以在单个过滤器文档中指定同一标识符的复合条件,如下面的示例所示
有关示例,请参阅使用
| ||||||||||||||||||
提示 | 文档或字符串 | 可选。一个文档或字符串,用于指定用于支持 该选项可以是一个索引规范文档或索引名称字符串。 如果您指定了一个不存在的索引,则操作会出错。 有关示例,请参阅为 | ||||||||||||||||||
注释 | 任何内容 | 可选。用户提供的注释,用于附加到该命令。一旦设置,此注释将出现在以下位置的该命令记录旁边
注释可以是任何有效的 BSON 类型(字符串、整数、对象、数组等)。 | ||||||||||||||||||
文档 | 可选。 指定一个包含变量列表的文档。这允许您通过将变量与查询文本分开来提高命令的可读性。 文档语法是
变量设置为表达式返回的值,之后不能更改。 要在命令中访问变量的值,请使用双美元符号前缀( 要使用变量来过滤结果,您必须在 有关使用 新版本5.0. |
输出
命令 findAndModify
返回具有以下字段的文档
字段 | 类型 | 描述 |
---|---|---|
value | 文档 | 包含命令返回的值。有关详细信息,请参阅 value 。 |
lastErrorObject | 文档 | 包含有关更新文档的信息。有关详细信息,请参阅 lastErrorObject 。 |
ok | number | 包含命令的执行状态。成功时为 1 ,如果发生错误则为 0 。 |
lastErrorObject
嵌入的 lastErrorObject
文档包含以下字段
字段 | 类型 | 描述 |
---|---|---|
n | 整数 | 包含匹配更新谓词的文档数量或插入或删除的文档数量。 |
updatedExisting | 布尔值 | 包含
|
upserted | 文档 |
值
对于 删除
操作,如果查询匹配到文档,则 值
包含被删除的文档。如果查询没有匹配到要删除的文档,则 值
包含 null
。
对于 更新
操作,内嵌的 值
文档包含以下内容
如果未设置
new
参数或其值为false
如果查询匹配到文档,则包含预修改的文档;
否则,包含
null
。
如果
new
的值为true
如果查询返回匹配,则包含更新的文档;
如果
upsert: true
且没有文档与查询匹配,则包含插入的文档;否则,包含
null
。
行为
使用唯一索引的Upsert
除非存在一个唯一索引来防止重复,否则Upsert可能会创建重复的文档。
考虑一个例子,没有名为Andy
的文档存在,并且多个客户端几乎同时发出以下命令
db.runCommand( { findAndModify: "people", query: { name: "Andy" }, update: { $inc: { score: 1 } }, upsert: true } )
如果所有findAndModify
操作在任何客户端成功插入数据之前完成查询阶段,并且name
字段上没有唯一索引,则每个findAndModify
操作可能会导致插入,创建多个name: Andy
的文档。
name
字段的唯一索引确保只能创建一个文档。有了唯一索引,现在多个findAndModify
操作表现出以下行为
只有一个
findAndModify
操作将成功插入一个新文档。其他
findAndModify
操作要么更新新插入的文档,要么由于唯一键冲突而失败。为了使其他
findAndModify
操作能够更新新插入的文档,以下所有条件都必须满足目标集合有一个会导致重复键错误的唯一索引。
更新操作不是
updateMany
,或者multi
设置为false
。更新匹配条件是
一个单个相等谓词。例如
{ "fieldA" : "valueA" }
相等谓词的逻辑AND。例如
{ "fieldA" : "valueA", "fieldB" : "valueB" }
相等谓词中的字段与唯一索引键模式中的字段匹配。
更新操作不修改唯一索引键模式中的任何字段。
以下表格显示了在键冲突发生时,要么更新要么失败的upsert
操作的示例。
唯一索引键模式 | 更新操作 | 结果 | ||||||
---|---|---|---|---|---|---|---|---|
|
| 匹配文档的 score 字段增加1。 | ||||||
|
| 操作失败,因为它修改了唯一索引键模式中的字段( name )。 | ||||||
|
| 操作失败,因为等价谓词字段( name 、email )与索引键字段(name )不匹配。 |
分片集合
要在分片集合上使用 findAndModify
如果您只针对一个分片,您可以在
query
字段中使用部分分片键,或者您可以在
query
字段中提供一个全部分片键的等价条件。从版本 7.1 开始,您不需要在查询规范中提供 分片键 或
_id
字段。
分片集合中的文档可能缺少分片键字段。为了针对缺少分片键的文档,您可以使用 null
等价匹配与另一个过滤条件(例如 _id
字段)结合使用。例如
{ _id: <value>, <shardkeyfield>: null } // _id of the document missing shard key
分片键修改
除非分片键字段是不可变的 _id
字段,否则您可以更新文档的分片键值。
注意
分片集合中的文档可能缺少分片键字段。在更改文档的分片键值时,请谨慎操作,以避免意外删除分片键。
要使用 findAndModify
:
缺少分片键
分片集合中的文档可能 缺少分片键字段。要使用 findAndModify
来设置文档的 缺失 分片键
提示
由于缺失的键值作为 null 相等匹配的一部分返回,为了避免更新具有 null 值的键,根据需要包含其他查询条件(例如,在 _id
字段上)。
另请参阅
文档验证
findAndModify
命令增加了对 bypassDocumentValidation
选项的支持,允许在具有验证规则的集合中插入或更新文档时跳过 文档验证。
与 update
方法的比较
在更新文档时,findAndModify
和 updateOne()
方法的工作方式不同
如果多个文档匹配更新条件,对于
findAndModify
,您可以指定一个sort
以提供对要更新哪个文档的控制updateOne()
更新第一个匹配的文档默认情况下,
findAndModify
返回一个包含文档修改前版本以及操作状态的对象. 要获取更新后的文档,使用new
选项updateOne()
方法返回一个WriteResult()
对象,其中包含操作状态要返回更新后的文档,使用
find()
方法。但是,其他更新可能在您的更新和文档检索之间修改了文档。此外,如果更新仅修改了一个文档但多个文档匹配,您需要使用额外的逻辑来识别更新的文档
当修改 单个 文档时,findAndModify
和 updateOne()
方法 原子性地 更新文档。有关这些方法的交互和操作顺序的更多详细信息,请参阅 原子性和事务
交易
findAndModify
可以在分布式事务中使用。
重要
在大多数情况下,分布式事务比单文档写入产生的性能成本更高,分布式事务的可用性不应替代有效的架构设计。在许多场景中,非规范化数据模型(嵌入文档和数组)将仍然是最适合您数据和用例的。也就是说,在许多场景中,适当地建模数据将最大限度地减少分布式事务的需求。
有关其他交易使用考虑事项(如运行时限制和oplog大小限制),请参阅生产注意事项。
事务内的Upsert
如果事务不是跨分片写入事务,则可以在分布式事务中创建集合和索引。
带有upsert: true
的findAndModify
可以在现有集合或不存在集合上运行。如果在不存在的集合上运行,则该操作将创建该集合。
写入关注点和事务
在事务中运行操作时,不要显式设置写关注。要使用事务与写关注,请参阅事务和写关注。
示例
更新并返回
以下命令更新了 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
字段值大于 10。排序操作符
sort
将查询结果按升序排列。如果多个文档满足query
条件,则按此sort
顺序选择第一个文档进行修改。更新操作符
update
通过链接increments
将score
字段的值增加1。该命令返回一个包含以下字段的文档
lastErrorObject
字段包含命令的详细信息,包括字段updatedExisting
,其值为true
,以及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
条件,则命令返回一个包含null
的value
字段的文档
{ "value" : null, "ok" : 1 }
mongosh
和许多驱动程序提供findAndModify()
辅助方法。使用shell辅助方法,此先前操作可以采用以下形式
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
以下findAndModify
命令包含用于更新操作的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 }
返回新文档
以下findAndModify
命令包含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
数组中所有成绩大于或等于 85
的 mean
。
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" } } )