3

我正在尝试使用文档数组作为参数调用 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)则不会引发错误。谢谢。

4

2 回答 2

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只是一个围绕 update 的辅助方法,因为它_id从文档中提取 并将其传递给update.

因此,虽然他们可以将功能作为一种方便的方法添加到控制台支持,但底层 MongoDB 数据库仍然不会直接支持该操作,因此它仍然会单独执行操作。一些驱动程序已经支持这一点——但它是一个接一个地完成的,而不是成批的。

无论如何,您想要的行为的等价物可以写在一行中:

[doc1, doc2, doc3].forEach(function(d) { db.docs.save(d) })
于 2013-11-05T13:40:32.050 回答
2

很可能这是一个错误。这很奇怪,但官方文档并没有说明有关使用保存与文档数组的任何内容。它只说明了大约 1 个文件:

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

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

再深入一点,可以看到 save 是通过以下方式实现的:

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]);
}
于 2013-11-05T12:00:47.423 回答