db.collection.findAndModify()

在本页面

定义

  • db.collection. findAndModify(文件)

    • 修改并返回单个文档。默认情况下,返回的文档不包括对更新所做的修改。要通过对更新进行的修改来回显文档,请使用new选项。 findAndModify()方法是findAndModify命令周围的 shell 助手。

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

更改了 version 3.6.

db.collection.findAndModify({
    query: <document>,
    sort: <document>,
    remove: <boolean>,
    update: <document>,
    new: <boolean>,
    fields: <document>,
    upsert: <boolean>,
    bypassDocumentValidation: <boolean>,
    writeConcern: <document>,
    collation: <document>,
    arrayFilters: [ <filterdocument1>, ... ]
});

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

Return 数据

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

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

  • 如果未设置new参数或false

    • 如果查询与文档匹配,则为 pre-modification 文档;

    • 否则,null

  • 如果newtrue

    • 修改后的文档,如果查询返回 match;

    • 插入的文档,如果upsert: true,没有文档与查询匹配;

    • 否则,null

更改 version 3.0:在以前的版本中,如果更新,sort已指定,upsert: truenew选项未设置或new: false,db.collection.findAndModify()将返回空文档{}而不是null

行为

Upsert 和 Unique Index

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

在下面的示例中,不存在 name Andy的文档,并且多个 clients 发出以下命令:

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

然后,如果这些 clients 的findAndModify()方法在任何命令启动modify阶段之前完成query阶段,name字段上没有唯一索引,则命令可以全部执行 upsert,创建多个重复文档。

要防止使用相同的 name 创建多个重复文档,请在name字段上创建独特的指数。有了这个唯一索引,多个方法将表现出以下行为之一:

  • 正好一个findAndModify()成功插入一个新文档。

  • 零个或多个findAndModify()方法更新新插入的文档。

  • 零个或多个findAndModify()方法在尝试 Insert 具有相同 name 的文档时失败。如果由于name字段上的唯一索引约束违规而导致方法失败,则可以重试该方法。如果没有删除文档,则重试不应失败。

Sharded Collections

在分片环境中使用findAndModify时,query 必须包含针对分片集合的分片 cluster 的所有操作的碎片 key。

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

从MongoDB 4.2开始,您可以更新文档的分片键值,除非分片键字段是不可变_id字段。有关更新分片键的详细信息,请参见更改文档的分片键值。

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

文件验证

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

与update方法的比较

更新文档时,findAndModify()和update()方法的操作方式不同:

  • 默认情况下,两个操作都会修改单个文档。但是,带有multi选项的update()方法可以修改多个文档。

  • 如果多个文档匹配更新条件,对于findAndModify(),您可以指定sort以提供对要更新的文档的某种控制措施。

使用update()方法的默认行为,您无法在多个文档 match 时指定要更新的单个文档。

  • 默认情况下,findAndModify()返回文档的 pre-modified version。要获取更新的文档,请使用new选项。

    update()方法返回包含操作状态的写结果 object。要 return 更新的文档,请使用find()方法。但是,其他更新可能已在您的更新和文档检索之间修改了文档。此外,如果更新仅修改了单个文档但匹配了多个文档,则需要使用其他逻辑来标识更新的文档。

修改单个文档时,findAndModify()和update()方法都会自动更新文档。有关这些方法的操作的交互和顺序的更多详细信息,请参阅原子性和 Transactions。

事务

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

重要

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

有关其他事务使用方面的注意事项(例如运行时限制和操作日志大小限制),另请参见 生产注意事项。

现有的收藏和交易

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

写的担忧和事务

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

例子

更新和 Return

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

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

此方法执行以下操作:

  • querypeople集合中查找name字段具有 value Tomstate字段具有 value activerating字段具有 value greater than 10 的文档。

  • sort以升序 order 命令查询结果。如果多个文档符合query条件,则该方法将选择修改此sort所订购的第一个文档。

  • 更新increments score字段的 value 为 1。

  • 该方法返回为此更新选择的原始(i.e.pre-modification)文档:

    {
        "_id" : ObjectId("50f1e2c99beb36a0f45c6453"),
        "name" : "Tom",
        "state" : "active",
        "rating" : 100,
        "score" : 5
    }

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

如果没有文档与query条件匹配,则该方法返回null

UPSERT

以下方法包括update选项的upsert: true选项,用于更新匹配的文档;如果不存在匹配的文档,则创建新文档:

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

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

如果方法找到匹配的文档,则该方法创建一个新文档。因为该方法包含sort选项,所以它返回一个空文档{ }作为原始(pre-modification)文档:

{ }

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

null

返回新文档

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

在以下 example 中,people集合中的任何文档都不匹配query条件:

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

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

{
    "_id" : ObjectId("50f49ad6444c11ac2448a5d6"),
    "name" : "Pascal",
    "rating" : 25,
    "score" : 1,
    "state" : "active"
}

排序和删除

通过在rating字段上包含sort规范,以下 example 将从people集合中删除state value 为active且匹配文档中最低rating的单个文档:

db.people.findAndModify(
    {
        query: { state: "active" },
        sort: { rating: 1 },
        remove: true
    }
)

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

{
    "_id" : ObjectId("52fba867ab5fdca1299674ad"),
    "name" : "XYZ123",
    "score" : 1,
    "state" : "active",
    "rating" : 3
}

指定排序规则

version 3.4 中的新内容。

整理允许用户为 string 比较指定 language-specific 规则,例如字母和重音标记的规则。

集合myColl具有以下文档:

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

以下操作包括整理选项:

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

该操作返回以下文档:

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

为 Array Update Operations 指定 arrayFilters

注意

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

version 3.6 中的新内容。

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

更新元素 Match arrayFilters Criteria

使用以下文档创建集合students

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

要修改grades array 中大于或等于100的所有元素,请使用过滤后的位置 operator $ [<identifier> ]和db.collection.findAndModify方法中的arrayFilters选项:

db.students.findAndModify({
    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 ] }

更新 Array 文档的特定元素

使用以下文档创建集合students2

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 }
        ]
    }
])

要修改grades array 中等级大于或等于85的所有元素的mean字段的 value,请使用过滤后的位置 operator $ [<identifier> ]和db.collection.findAndModify方法中的arrayFilters

db.students2.findAndModify({
    query: { },
    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 }
    ]
}

使用聚合管道进行更新

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

  • $addFields及其别名 $set

  • $project及其别名 $unset

  • $replaceRoot及其别名$replaceWith

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

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

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字段中计算一个新 字段:

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。

该操作返回_更新的_文档:

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

也可以看看

可线性化通过 findAndModify 读取

译者:李冠飞

校对:

最后更新于