如何更新一个MongoDB文档的_id?


129

我想更新_id一个文档的字段。我知道这不是一个很好的实践。但是由于某些技术原因,我需要对其进行更新。如果我尝试更新它,则会得到:

> db.clients.update({ _id: ObjectId("123")}, { $set: { _id: ObjectId("456")}})

Performing an update on the path '_id' would modify the immutable field '_id'

并且没有进行更新。我该如何更新?

Answers:


216

您无法更新。您必须使用new来保存文档_id,然后删除旧文档。

// store the document in a variable
doc = db.clients.findOne({_id: ObjectId("4cc45467c55f4d2d2a000002")})

// set a new _id on the document
doc._id = ObjectId("4c8a331bda76c559ef000004")

// insert the document, using the new _id
db.clients.insert(doc)

// remove the document with the old _id
db.clients.remove({_id: ObjectId("4cc45467c55f4d2d2a000002")})

29
如果该文档上的某些字段具有唯一索引,则会出现一个有趣的问题。在这种情况下,您的示例将失败,因为无法在唯一索引字段中插入具有重复值的文档。您可以先执行删除操作来解决此问题,但这不是一个好主意,因为如果由于某种原因插入失败,您的数据现在将会丢失。相反,您必须删除索引,执行工作,然后还原索引。
skelly 2014年

好点@skelly!我碰巧想到了类似的问题,并在2小时前看到您的新评论。因此,这种修改ID的麻烦被视为是由于允许用户选择ID而引起的内在问题?
RayLuo 2014年

1
如果你得到duplicate key errorinsert线,而不是担心@skelly提到的问题,最简单的办法是只调用remove第一行,然后调用insert线。本doc应该已经印在屏幕上,因此会很容易恢复,最坏的情况下,即使插入失败,为简单的文档。
philfreo 2015年

仅使用不带字符串作为参数的ObjectId()会生成一个新的唯一的。
Erik 2015年

1
@ShankhadeepGhoshal是的,这是一种风险,特别是如果您是针对实时生产系统执行此操作的话。不幸的是,我认为您最好的选择是按计划进行停机,并在此过程中停止所有写入程序。可能会稍微减轻一些痛苦的另一种方法是暂时将应用程序强制为只读模式。重新配置所有写入数据库的应用程序,使其仅指向辅助节点。在此期间,读取将成功,但是写入将失败,并且数据库将保持静态。
skelly

32

为此,您还可以使用循环(基于Niels示例):

db.status.find().forEach(function(doc){ 
    doc._id=doc.UserId; db.status_new.insert(doc);
});
db.status_new.renameCollection("status", true);

在这种情况下,UserId是我要使用的新ID


1
是否建议在找到快照()时使forEach避免在迭代过程中意外获取新文档?
John Flinchbaugh 2014年

此代码段永远不会完成。它会永远不断地遍历整个集合。快照无法达到您的期望(可以通过“快照”将文档添加到集合中,然后查看新文档是否在快照中来进行测试)
Patrick

有关快照的替代方法,请参见stackoverflow.com/a/28083980/305324list()是合乎逻辑的,但是对于大型数据库,这可能会耗尽内存
Patrick

4
嗯,这有11个投票,但有人说这是一个无限循环?怎么了
安德鲁

4
@Andrew,因为现代流行编码器文化要求您在实际验证输入是否有效之前应始终确认输入良好。
csvan 2015年

5

如果您想在同一集合中重命名_id(例如,如果要给某些_id加上前缀):

db.someCollection.find().snapshot().forEach(function(doc) { 
   if (doc._id.indexOf("2019:") != 0) {
       print("Processing: " + doc._id);
       var oldDocId = doc._id;
       doc._id = "2019:" + doc._id; 
       db.someCollection.insert(doc);
       db.someCollection.remove({_id: oldDocId});
   }
});

if(doc._id.indexOf(“ 2019:”)!= 0){...需要防止无限循环,因为forEach会选择插入的文档,甚至使用.snapshot()方法也是如此。


find(...).snapshot is not a function但是除此之外,很棒的解决方案。另外,如果您要替换_id为自定义ID,则可以检查是否doc._id.toString().length === 24防止无限循环(假设您的自定义ID的长度也不是24个字符),
Dan Dascalescu

1

在这里,我有一个避免重复请求和循环删除旧文档的解决方案。

您可以使用类似以下的命令轻松地手动创建一个新的想法:_id:ObjectId() 但是,如果知道Mongo会自动分配一个_id(如果缺少),则可以使用aggregate创建一个$project包含文档的所有字段的字段,但是忽略字段_id。然后可以用保存$out

因此,如果您的文档是:

{
"_id":ObjectId("5b5ed345cfbce6787588e480"),
"title": "foo",
"description": "bar"
}

然后您的查询将是:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])

有趣的主意...但是您通常希望将设置为_id给定值,而不是让MongoDB生成另一个值。
Dan Dascalescu

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.