db.collection.findAndModify()

定义

findAndModify()方法具有以下形式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
db.collection.findAndModify({
    query: <document>,
    sort: <document>,
    remove: <boolean>,
    update: <document or aggregation pipeline>, // Changed in MongoDB 4.2
    new: <boolean>,
    fields: <document>,
    upsert: <boolean>,
    bypassDocumentValidation: <boolean>,
    writeConcern: <document>,
    collation: <document>,
    arrayFilters: [ <filterdocument1>, ... ]
});

该db.collection.findAndModify()方法采用带有以下嵌入式文档字段的文档参数:

参数 类型 描述
query 文件 可选的。修改的选择标准。该query字段和db.collection.find()方法采用相同的查询选择器.db.collection.findAndModify()尽管查询可能匹配多个文档,但只会选择一个文档进行修改。如果未指定,则默认为空文档。从MongoDB 4.2(和4.0.12 +,3.6.14 +和3.4.23+)开始,如果查询参数不是文档,则操作错误。
sort 文件 可选的。确定如果查询选择多个文档,则操作将修改哪个文档。db.collection.findAndModify()按照此参数指定的排序顺序修改第一个文档。从MongoDB 4.2(和4.0.12 +,3.6.14 +和3.4.23+)开始,如果sort参数不是文档,则操作会出错。
remove 布尔值 必须指定remove或update字段。删除在query字段中指定的文档。设置此项以true 删除选定的文档。默认值为false。
update 文件或数组 必须指定remove或update字段。执行所选文档的更新。如果传递带有update运算符表达式的文档,则db.collection.findAndModify()执行指定的修改。如果通过了替换文件,则执行替换。{ <field1>: <value1>, ...}.在MongoDB中4.2开始,如果通过一个汇聚管道 , 修改每个管道中的文档。管道可以包括以下阶段:[ <stage1>, <stage2>, ... ],$addFields 及其别名, $set,$project 及其别名 $unset,$replaceRoot及其别名$replaceWith
new 布尔值 可选的。当为true时,返回修改后的文档而不是原始文档。若remove为true,new选项被忽略。默认值为false。
fields 文献 可选的。要返回的字段子集。该fields文档使用来指定包含一个字段,见投影。fields: { <field1>: 1, <field2>: 1, ... }.从MongoDB 4.2(和4.0.12 +,3.6.14 +和3.4.23+)开始,如果fields参数不是文档,则操作错误。
upsert 布尔值 可选的。与update现场配合使用。当true,findAndModify()如果没有文档与匹配,则创建一个新文档query。更新与匹配的单个文档query。为避免多次更新,请确保query字段被唯一索引。默认为false。
bypassDocumentValidation 布尔值 可选的。允许db.collection.findAndModify在操作过程中绕过文档验证。这使您可以更新不满足验证要求的文档。3.2版中的新功能。
writeConcern 文献 可选的。表达书面关切的文件。省略使用默认的写关注。如果在事务中运行,则不要为操作明确设置写关注点。要对事务使用写关注,请参见 事务和写关注。3.2版中的新功能。
maxTimeMS 整数 可选的。指定用于处理操作的时间限制(以毫秒为单位)。
collation 文档 可选的。指定 用于操作的排序规则。归类允许用户为字符串比较指定特定于语言的规则,例如字母大写和重音符号的规则。排序规则选项具有以下语法:{locale: <string>,caseLevel: <boolean>,caseFirst: <string>,strength: <int>,numericOrdering: <boolean>,alternate: <string>,maxVariable: <string>,backwards: <boolean>}指定排序规则时,该locale字段为必填字段;所有其他排序规则字段都是可选的。有关字段的说明,请参见整理文档。如果未指定排序规则,但是集合具有默认排序规则(请参阅参考资料db.createCollection()),则该操作将使用为集合指定的排序规则。如果没有为集合或操作指定排序规则,则MongoDB使用先前版本中使用的简单二进制比较进行字符串比较。您不能为一个操作指定多个排序规则。例如,您不能为每个字段指定不同的排序规则,或者如果对排序执行查找,则不能对查找使用一种排序规则,而对排序使用另一种排序规则。
arrayFilters 数组 可选的。筛选器文档数组,用于确定要对数组字段进行更新操作要修改的数组元素。在更新文档中,使用$[<identifier>]过滤后的位置运算符定义一个标识符,然后在数组过滤器文档中引用该标识符。如果该标识符未包含在更新文档中,则不能具有标识符的数组过滤器文档。注意,在<identifier>必须以小写字母开头,并且只包含字母数字字符。您可以在更新文档中多次包含相同的标识符;但是,对于$[identifier]更新文档中的每个不同的标识符(),必须精确地指定一个 对应的数组过滤器文档。也就是说,您不能为同一标识符指定多个数组过滤器文档。

返回数据

对于删除操作,如果查询与文档匹配,则 findAndModify()返回删除的文档。如果查询与要删除的文档不匹配,则 findAndModify()返回null。

对于更新操作,findAndModify()返回以下之一:

  • 如果new参数未设置或为false:
    • 如果查询与文档匹配,则为修改前文档;
    • 否则,null。
  • 如果new是true:
    • 如果查询返回匹配项,则修改后的文档;
    • 如果没有文档匹配查询且upsert: true,则插入文档;
    • 否则,null。

特性

Upsert和唯一索引

当findAndModify()包含选项upsert: true并且查询字段不是唯一索引时,该方法在某些情况下可以多次插入文档。

在以下示例中,不存在具有名称Andy的文档,并且多个客户端发出以下命令:

1
2
3
4
5
6
db.people.findAndModify({
    query: { name: "Andy" },
    sort: { rating: 1 },
    update: { $inc: { score: 1 } },
    upsert: true
})

然后,如果这些客户机的findAndModify() 方法query在任何命令开始阶段之前完成了该 modify阶段,并且该name 字段上没有唯一索引,则这些命令可能全部执行upsert,从而创建多个重复文档。

为防止创建多个具有相同名称的重复文档,请在 字段上创建唯一索引name。有了此唯一索引,多种方法将表现出以下特性之一:

  • 恰好findAndModify() 成功插入了一个新文档。
  • 零个或多个findAndModify()方法将更新新插入的文档。
  • findAndModify()尝试插入相同名称的文档时,零个或多个方法失败。如果该方法由于在name字段上违反唯一索引约束而失败 ,则可以重试该方法。如果没有删除文档,重试应该不会失败。

分片集合

当使用findAndModify在分片环境中,query 必须包含关于平等条件 片键针对用于分片的集群中的所有操作分片集合。

findAndModify针对非分片集合的mongos 实例发出的操作正常运行。

从MongoDB 4.2开始,除非分片键字段是不可变_id字段,否则您可以更新文档的分片键值。

在MongoDB 4.2之前,文档的分片键字段值是不可变的。

文档验证

该db.collection.findAndModify()方法增加了对该bypassDocumentValidation选项的支持 ,使您可以在使用验证规则向集合中插入或更新文档时绕过文档验证。

与update方法的比较

当更新一个文件,db.collection.findAndModify()和update()方法有不同操作:

  • 默认情况下,这两种操作都会修改单个文档。但是update()方法及其multi选项可以修改多个文档。
  • 如果多个文档符合的更新条件,则 db.collection.findAndModify()可以指定sort来提供对要更新的文档的某种控制措施。
  • 使用该update() 方法的默认特性,当多个文档匹配时,您无法指定要更新哪个文档。
  • 默认情况下,db.collection.findAndModify()返回文档的预修改版本。要获取更新的文档,请使用new选项。
  • 该update()方法返回一个 WriteResult包含操作状态的对象。若要返回更新的文档,请使用find() 方法。但是,其他更新可能已在您的更新和文档检索之间修改了文档。另外,如果更仅修改了一个文档,但匹配了多个文档,则您将需要使用其他逻辑来标识更新后的文档。
  • 修改单个文档时,两者db.collection.findAndModify()和 update()方法都会原子性的更新文档。

事务

db.collection.findAndModify()可以在多文档事务中使用。

重要

在大多数情况下,与单文档写入相比,多文档事务产生的性能成本更高,并且多文档事务的可用性不应代替有效的架构设计。在许多情况下, 非规范化数据模型(嵌入式文档和数组)对于您的数据和用例将继续是最佳的。也就是说,在许多情况下,对数据进行适当的建模将最大程度地减少对多文档事务的需求。

现有的集合和事务

在事务内部,您可以指定对现有集合的读/写操作。如果db.collection.findAndModify()使用upsert,则该集合必须已经存在。

写关注和事务

如果在事务中运行,则不要为操作明确设置写关注点。要对事务使用写关注,请参见 事务和写关注。

例子

更新和返回

以下方法更新并返回人员集合中与查询条件相匹配的现有文档:

1
2
3
4
5
db.people.findAndModify({
    query: { name: "Tom", state: "active", rating: { $gt: 10 } },
    sort: { rating: 1 },
    update: { $inc: { score: 1 } }
})

此方法执行以下操作:

  1. 在query找到的文件people,其中收集name字段的值Tom,该state字段值为active和rating字段的值大于10
  2. 该sort订单升序排序查询的结果。如果多个文档满足query条件,则该方法将选择按此命令修改的第一个文档sort。
  3. 用1更新字段increments的score值。
  4. 该方法返回为此更新选择的原始(即预先修改)文档:
    1
    2
    3
    4
    5
    6
    7
    
    {
      "_id" : ObjectId("50f1e2c99beb36a0f45c6453"),
      "name" : "Tom",
      "state" : "active",
      "rating" : 100,
      "score" : 5
    }
    

要返回修改后的文档,请将new:true选项添加到方法中。

如果没有文档符合query条件,则该方法返回null。

UPSERT

以下方法包括用于 更新匹配文档或如果不存在匹配文档的话,创建新文档的操作选项:upsert: true.

1
2
3
4
5
6
db.people.findAndModify({
    query: { name: "Gus", state: "active", rating: 100 },
    sort: { rating: 1 },
    update: { $inc: { score: 1 } },
    upsert: true
})

如果该方法找到匹配的文档,则该方法将执行更新。

如果该方法没有找到匹配的文件,该方法将创建一个新文档。由于该方法包括该sort选项,因此它返回一个空文档作为原始(预修改)文档:{ }

1
{ }

如果该方法不包含sort选项,则该方法返回 null。

1
null

返回新建文档

以下方法同时包含new:true选项和upsert: true选项。该方法或者更新匹配的文档并返回更新的文档,或者,如果不存在匹配的文档,则插入文档并在字段中返回新插入的文档

在以下示例中,people集合中没有任何文档符合query条件:

1
2
3
4
5
6
7
db.people.findAndModify({
    query: { name: "Pascal", state: "active", rating: 25 },
    sort: { rating: 1 },
    update: { $inc: { score: 1 } },
    upsert: true,
    new: true
})

该方法返回新插入的文档:

1
2
3
4
5
6
7
{
   "_id" : ObjectId("50f49ad6444c11ac2448a5d6"),
   "name" : "Pascal",
   "rating" : 25,
   "score" : 1,
   "state" : "active"
}

排序和删除

通过sort在rating字段中包含一个规范,以下示例从people集合中删除单个文档,该单个文档的state值active等于rating匹配文档中的最低值:

1
2
3
4
5
6
7
db.people.findAndModify(
   {
     query: { state: "active" },
     sort: { rating: 1 },
     remove: true
   }
)

该方法返回已删除的文档:

1
2
3
4
5
6
7
{
   "_id" : ObjectId("52fba867ab5fdca1299674ad"),
   "name" : "XYZ123",
   "score" : 1,
   "state" : "active",
   "rating" : 3
}

指定归类

3.4版的新功能。

归类允许用户为字符串比较指定特定于语言的规则,例如字母大写和重音符号的规则。

集合myColl包含以下文档:

1
2
3
{ _id: 1, category: "café", status: "A" }
{ _id: 2, category: "cafe", status: "a" }
{ _id: 3, category: "cafE", status: "a" }

以下操作包括排序规则 选项:

1
2
3
4
5
6
db.myColl.findAndModify({
    query: { category: "cafe", status: "a" },
    sort: { category: 1 },
    update: { $set: { status: "Updated" } },
    collation: { locale: "fr", strength: 1 }
});

该操作返回以下文档:

1
{ "_id" : 1, "category" : "café", "status" : "A" }

指定arrayFilters更新数组

注意:arrayFilters 不适用于使用聚合管道的更新。

3.6版的新功能。

从MongoDB 3.6开始,在更新数组字段时,您可以指定arrayFilters确定要更新的数组元素。

更新元素匹配arrayFilters条件

使用以下文档创建一个集合:

1
2
3
4
5
db.students.insert([
   { "_id" : 1, "grades" : [ 95, 92, 90 ] },
   { "_id" : 2, "grades" : [ 98, 100, 102 ] },
   { "_id" : 3, "grades" : [ 95, 110, 100 ] }
])

要修改大于或等于所有元素100中 grades数组,使用 $[<identifier>]arrayFilters在选项 db.collection.findAndModify方法:

1
2
3
4
5
db.students.findAndModify({
   query: { grades: { $gte: 100 } },
   update: { $set: { "grades.$[element]" : 100 } },
   arrayFilters: [ { "element": { $gte: 100 } } ]
})

该操作将更新grades单个文档的字段,并且在操作之后,集合将包含以下文档:

1
2
3
4
5
{ "_id" : 1, "grades" : [ 95, 92, 90 ] }

{ "_id" : 2, "grades" : [ 98, 100, 100 ] }

{ "_id" : 3, "grades" : [ 95, 110, 100 ] }

更新文档数组的特定元素

使用以下文档创建一个集合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
db.students2.insert([
   {
      "_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数组中所有grade大于或等于85的元素的mean。

1
2
3
4
5
db.students2.findAndModify({
   query: { _id : 1 },
   update: { $set: { "grades.$[elem].mean" : 100 } },
   arrayFilters: [ { "elem.grade": { $gte: 85 } } ]
})

该操作将更新grades单个文档的字段,并且在操作之后,集合将包含以下文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
   "_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 }
   ]
}

使用聚合管道进行更新

从MongoDB 4.2开始,db.collection.findAndModify()可以接受聚合管道进行更新。管道可以包括以下阶段:

  • $addFields 及其别名 $set
  • $project 及其别名 $unset
  • $replaceRoot及其别名$replaceWith

使用聚合管道可以实现更具表达力的更新语句,例如根据当前字段值表达条件更新,或使用另一个字段的值更新一个字段。

例如,students2使用以下文档创建一个集合:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
db.students2.insert([
   {
      "_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的文档,并使用聚合管道total从该grades字段中计算一个新字段:

1
2
3
4
5
db.students2.findAndModify( {
   query: {  "_id" : 1 },
   update: [ { $set: { "total" : { $sum: "$grades.grade" } } } ],  // The $set stage is an alias for ``$addFields`` stage
   new: true
} )

注意

$set管道中的使用是指聚集阶段$set,而不是更新操作$set

该操作返回更新的文档:

1
2
3
4
5
{
   "_id" : 1,
   "grades" : [ { "grade" : 80, "mean" : 75, "std" : 6 }, { "grade" : 85, "mean" : 90, "std" : 4 }, { "grade" : 85, "mean" : 85, "std" : 6 } ],
   "total" : 250
}