当我尝试对docs数组执行db.collection.save操作时,Mongo会抛出E11000重复键错误

时间:2013-11-05 11:21:27

标签: mongodb

我正在尝试使用文档数组作为参数调用mongodb的db.collection.save方法。如果存在_id,我想进行批量操作插入/替换。

这是我的测试用例:

> use sometestdb
switched to db sometestdb
> 
> doc1 = { _id: 1, value: "some value 1" }
{ "_id" : 1, "value" : "some value 1" }
> doc2 = { _id: 2, value: "some value 2" }
{ "_id" : 2, "value" : "some value 2" }
> doc3 = { _id: 3, value: "some value 3" }
{ "_id" : 3, "value" : "some value 3" }
> 
> db.docs.save( [doc1, doc2, doc3] )
> 
> doc1 = { _id: 1, value: "some value 1 - updated" }
{ "_id" : 1, "value" : "some value 1 - updated" }
> doc2 = { _id: 2, value: "some value 2 - updated" }
{ "_id" : 2, "value" : "some value 2 - updated" }
> doc3 = { _id: 3, value: "some value 3 - updated" }
{ "_id" : 3, "value" : "some value 3 - updated" }
> db.docs.save( [doc1, doc2, doc3] )
E11000 duplicate key error index: sometestdb.docs.$_id_  dup key: { : 1.0 }

如果我尝试拨打db.docs.save (doc1)db.docs.save (doc2),则不会引发任何错误。 THX。

2 个答案:

答案 0 :(得分:3)

MongoDB不支持在单个调用中批量更新多个文档。虽然这种行为可能看起来很不寻常,但是控制台的JavaScript部分尝试完全按照您的要求执行,即使我怀疑它是无意的,因为在第一种情况下使用insert会更有效。

当你:

db.docs.save( [doc1, doc2, doc3] )

MongoDB遍历数组并创建每个文档:

> db.docs.find()
{ "_id" : 1, "value" : "some value 1" }
{ "_id" : 2, "value" : "some value 2" }
{ "_id" : 3, "value" : "some value 3" }

如果您要重复此操作,您会发现:

> db.docs.save([doc1,doc2,doc3])
E11000 duplicate key error index: test.docs.$_id_  dup key: { : 1.0 }

这至少是部分意义上的,因为你不能在一个集合中insert两次同一个文档:

> db.docs.insert(doc1)
E11000 duplicate key error index: test.docs.$_id_  dup key: { : 1.0 }

由于MongoDB中没有针对多个文档的高效“批量”更新(您可以一次更新多个文档,这些文档都匹配单个查询,但您无法通过传入数组来更新单个文档,至少通过控制台)。

save只是更新的辅助方法,因为它从文档中提取_id并将其传递给update

因此,虽然他们可以将功能作为便捷方法添加到控制台支持,但基础MongoDB数据库仍然不能直接支持该操作,因此它仍然会单独执行操作。一些司机已经支持这一点 - 但它是逐个而不是批量完成的。

无论如何,相当于你想要的行为可以写成一行:

[doc1, doc2, doc3].forEach(function(d) { db.docs.save(d) })

答案 1 :(得分:2)

最有可能这是一个错误。这很奇怪,但是官方documentation并没有告诉任何关于在文档数组中使用save的事情。它只是陈述了大约1个文件:

  

更新现有文档或插入新文档,具体取决于   它的文档参数。

因此,这可能是一个未记录的功能,您可以传递数组: - )

深入了解,您可以看到以下列方式实施保存:

function ( obj ){
    if ( obj == null || typeof( obj ) == "undefined" )
        throw "can't save a null";

    if ( typeof( obj ) == "number" || typeof( obj) == "string" )
        throw "can't save a number or string"

    if ( typeof( obj._id ) == "undefined" ){
        obj._id = new ObjectId();
        return this.insert( obj );
    }
    else {
        return this.update( { _id : obj._id } , obj , true );
    }
}

对我们来说有趣的是第三个:

    if ( typeof( obj._id ) == "undefined" ){
        obj._id = new ObjectId();
        return this.insert( obj );
    }

当您第一次通过[doc1, doc2, doc3]时,typeof( [doc1, doc2, doc3]._id )undefined,因此会执行insert。并且insert按元素插入数组元素。

问题在于,当您下次传递它时,它仍然是未定义的,并且还执行插入并将错误作为重复键获取。但是如果你只保存一个文档,那么第三个块将不会返回undefined,从而执行更新。

尽管如此,现在可以理解为什么它以这种方式表现,我认为文档含糊不清。

无论如何,您可以通过以下方式实现您想要的目标:

var list = [doc1, doc2, doc3] ;
for (var i =0; i< list.length; i++){
  db.docs.save(list[i]);
}