如何限制MongoDB中更新文档的数量

68

如何实现类似于 db.collection.find().limit(10) 的功能,但同时更新文档呢?

我目前使用的方法非常糟糕,即先使用 db.collection.find().limit() 获取文档,再进行更新。

总体上来说,我想要返回给定数量的记录,并更改每个记录中的一个字段。

谢谢。


8
请注意:这件事情已有一个工单在处理中(自2010年,版本号为1.6.0)。你可以在https://jira.mongodb.org/browse/SERVER-1599找到它,但暂时没有计划安排它进行发布。 - zamnuts
3
此工单已经修复,但实际上并不涉及在更新操作中添加限制(只有 $hint 有关)。 - Arthur
8个回答

48

您可以使用:

db.collection.find().limit(NUMBER_OF_ITEMS_YOU_WANT_TO_UPDATE).forEach(
    function (e) {
        e.fieldToChange = "blah";
        ....
        db.collection.save(e);
    }
);

(forEach 代码由 MongoDB:使用来自同一文档的数据更新文档提供)

这将只更改指定数量的条目。例如,如果您想将名为“newField”的字段和值1添加到“collection”中的一半条目,可以输入:

db.collection.find().limit(db.collection.count() / 2).forEach(
    function (e) {
        e.newField = 1;
        db.collection.save(e);
    }
);
如果您想让另一半也有值为2的"newField",则可以使用更新操作,并设置条件为 newField 不存在:
db.collection.update( { newField : { $exists : false } }, { $set : { newField : 2 } }, {multi : true} );

5
在这种情况下,将发送多少个查询到Mongo服务器?是1个用于查找和N个用于查找结果中的N个吗? - Andrey
5
真的吗?这里没有人注意到N+1 dB调用问题吗? - tchelidze

22

使用forEach逐个更新每个文档的速度较慢。您可以使用批量方式更新文档。

ids = db.collection.find(<condition>).limit(<limit>).map(
    function(doc) {
        return doc._id;
    }
);
db.collection.updateMany({_id: {$in: ids}}, <update>})

1
如果您使用此方法,应在查询中添加投影以提高性能(您只需要_id):db.collection.find(<condition>, {_id: 1})。 - taxilian
当我尝试这样做时,它会显示:[ASYNC-10007] 无法将 Mongosh API 对象作为参数传递给函数。 - FelixHJ

15
所有对象都迭代然后逐个更新的解决方案非常慢。
使用$in一次检索所有对象,然后同时更新它们更加高效。
ids = People.where(firstname: 'Pablo').limit(10000).only(:_id).to_a.map(&:id)
People.in(_id: ids).update_all(lastname: 'Cantero')

查询使用的是 Mongoid 来编写,但也可以很容易地在 Mongo Shell 中重写。


12

很不幸,据我所知,你拥有的解决方法是唯一的方法。有一个布尔标志multi,当为true时将更新所有匹配项,否则将只更新第一个匹配项。


3

正如答案所述,目前还没有办法将要更新(或删除)的文档数量限制为大于1的值。一个解决方法是使用类似以下的方式:

db.collection.find(<condition>).limit(<limit>).forEach(function(doc){db.collection.update({_id:doc._id},{<your update>})})

1

我最近也需要类似的东西。我认为查询一长串_id然后在$in中进行更新可能也很慢,所以我尝试使用聚合+合并。

while (true) {
    const record = db.records.findOne({ isArchived: false }, {_id: 1})
    if (!record) {
        print("No more records")
        break
    }
    
    db.records.aggregate([
        { $match: { isArchived: false } },
        { $limit: 100 },
        { 
            $project: { 
                _id: 1, 
                isArchived: { 
                    $literal: true 
                }, 
                updatedAt: {
                    $literal: new Date()      
                } 
            } 
        },
        { 
            $merge: {
                into: "records",
                on: "_id",
                whenMatched: "merge"
            }
        }
    ])   
    print("Done update")
}

但是如果使用$in进行批量更新比这种方法更好或更差,请随意发表评论。


0
    let fetchStandby = await db.model.distinct("key",{});
    fetchStandby = fetchStandby.slice(0, no_of_docs_to_be_updated)
    let fetch = await db.model.updateMany({
        key: { $in: fetchStandby }
    }, {
        $set:{"qc.status": "pending"}
    })

0
如果您的id是一个序列号而不是ObjectId,您可以在for循环中这样做:
let batchSize= 10;
for (let i = 0; i <= 1000000; i += batchSize) { 
  db.collection.update({$and :[{"_id": {$lte: i+batchSize}}, {"_id": {$gt: i}}]}),{<your update>})
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接