使用 Mongoose 在 MongoDB 中更新许多记录的正确方法是啥

Posted

技术标签:

【中文标题】使用 Mongoose 在 MongoDB 中更新许多记录的正确方法是啥【英文标题】:What is the right approach to update many records in MongoDB using Mongoose使用 Mongoose 在 MongoDB 中更新许多记录的正确方法是什么 【发布时间】:2016-12-09 02:37:10 【问题描述】:

我正在使用 Mongoose 从 MongoDB 中提取一些记录,将它们导入另一个系统,然后我想将所有这些文档的状态(文档属性)设置为 processed

我可以找到这个解决方案:Update multiple documents by id set. Mongoose

我想知道这是否是正确的方法,建立一个包含所有文档 ID 的标准,然后执行更新。还请考虑一个事实,即它将是许多文件。

(更新查询的限制是什么?到处都找不到。官方文档:http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html

【问题讨论】:

猫鼬支持Model.bulkWrite() 【参考方案1】:

建立一个包含所有文档 ID 的标准然后执行更新的方法必然会导致潜在的问题。当您使用每个文档迭代发送更新操作的文档列表时,在 Mongoose 中,您冒着炸毁服务器的风险,尤其是在处理大型数据集时,因为您无需等待异步调用完成,然后再继续下一个迭代。您将实质上构建一个未解决操作的“堆栈”,直到这导致问题 - ***。

例如,假设您有一个文档 ID 数组,您想更新状态字段上的匹配文档:

const processedIds = [
  "57a0a96bd1c6ef24376477cd",
  "57a052242acf5a06d4996537",
  "57a052242acf5a06d4996538"
];

您可以在哪里使用updateMany() 方法

Model.updateMany(
   _id:  $in: processedIds  , 
   $set:  status: "processed"  , 
  callback
);

或者对于非常小的数据集,您可以在数组上使用 forEach() 方法来迭代它并更新您的集合:

processedIds.forEach(function(id))
  Model.update( _id: id,  $set:  status: "processed"  , callback);
);

上述方法适用于小型数据集。但是,当您面对成千上万个要更新的文档时,这会成为一个问题,因为您将在循环中重复服务器调用异步代码。

要克服这个问题,请使用异步的 eachLimit 之类的方法,并遍历数组,为每个项目执行 MongoDB 更新操作,同时永远不会同时执行超过 x 个并行更新。


最好的方法是为此使用批量 API,这在批量处理更新方面非常有效。性能与对众多文档中的每一个都调用更新操作的区别在于,批量 API 不是在每次迭代时向服务器发送更新请求,而是在每 1000 个请求(批处理)中发送一次请求。

对于支持 MongoDB Server 3.2.x 的 Mongoose 版本 >=4.3.0,您可以使用 bulkWrite() 进行更新。下面的例子展示了如何去做:

const bulkUpdateCallback = function(err, r)
  console.log(r.matchedCount);
  console.log(r.modifiedCount);


// Initialize the bulk operations array
const bulkUpdateOps = [], counter = 0;

processedIds.forEach(function (id) 
  bulkUpdateOps.push(
    updateOne: 
      filter:  _id: id ,
      update:  $set:  status: "processed"  
    
  );
  counter++;

  if (counter % 500 == 0) 
    // Get the underlying collection via the Node.js driver collection object
    Model.collection.bulkWrite(bulkUpdateOps,  ordered: true, w: 1 , bulkUpdateCallback);
    bulkUpdateOps = []; // re-initialize
  
)

// Flush any remaining bulk ops
if (counter % 500 != 0) 
  Model.collection.bulkWrite(bulkOps,  ordered: true, w: 1 , bulkUpdateCallback);


对于支持 MongoDB Server >=2.6.x 的 Mongoose 版本 ~3.8.8~3.8.224.x,您可以使用 Bulk API,如下所示

var bulk = Model.collection.initializeOrderedBulkOp(),
    counter = 0;

processedIds.forEach(function(id) 
    bulk.find( "_id": id ).updateOne( 
        "$set":  "status": "processed" 
    );

    counter++;
    if (counter % 500 == 0) 
        bulk.execute(function(err, r) 
           // do something with the result
           bulk = Model.collection.initializeOrderedBulkOp();
           counter = 0;
        );
    
);

// Catch any docs in the queue under or over the 500's
if (counter > 0) 
    bulk.execute(function(err,result) 
       // do something with the result here
    );

【讨论】:

您介意告诉我bulkWriteinsertMany 有何不同吗? 或者collection.insertcollection.bulkWrite 有何不同?我似乎找不到关于这些东西的任何官方文档 :( 参考:unknownerror.org/opensource/Automattic/mongoose/q/***/… insertMany() 是在 Mongoose 4.4 及更高版本中使用 mongodb 驱动程序进行批量写入的新方法,而 bulkWrite() 将在未来的某个时候得到支持 #3998。基本上,insertMany 在后台使用Model.collection.insertMany()。我可以确定的主要区别是bulkWrite() 方法提供了执行批量插入、更新和删除操作的能力,而insertMany() 只支持批量插入操作。 Doc reference. 这很好用。我添加的唯一内容是在 bulkUpdateCallback 计算所有行都已处理时对调用函数的最终回调。否则很难将这些计数器合并到其他迭代模式中,因为它们到达太晚而无法返回。我发现异步模式通常如何为批量/迭代过程带来挑战,这很有趣——这是一个很棒的混合解决方案。 这种方法真的很有帮助......我花了几天时间寻找正确的解决方案......这对我有用......谢谢...... :)【参考方案2】:

要更新许多记录,据我所知,$in 是最好的选择。

db.collectionName.updateMany(

    _id:
        
            $in:
                [
                    ObjectId("your object id"),
                    ObjectId("your object id")

                ]
        
,

    $inc:  quantity: 100 

)

我想再补充一点,你可以使用$in来获取多个文档

db.collectionName.find(
        
            _id:
                
                    $in:
                        [
                            ObjectId("your object id"),
                            ObjectId("your object id")

                        ]
                
        )

【讨论】:

这是 MongoDB,不是 mongoose,$in 已经讨论过 in the top answer。这个答案究竟对现有答案增加了什么?【参考方案3】:

您可以在更新查询中使用 multi: true 选项进行批量更新。

示例

employees.update( _id:  $gt: 3  ,$inc:  sortOrder: -1 ,'multi':true);

mongoose中的上述代码等价于mongodb中的以下代码:

db.employees.updateMany( _id:  $gt: 3  ,$inc:  sortOrder: -1 );

【讨论】:

以上是关于使用 Mongoose 在 MongoDB 中更新许多记录的正确方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

使用mongoose在mongoDB中更新数组的数组

如何在 mongodb/mongoose 的嵌套数组中检查是不是存在并更新对象?

MongoDB(Mongoose)更新数组数组中的元素

如何在 mongoose 和 mongodb 中更新嵌套模型

通过在 body、mongoose/mongodb 中提供文档来更新多个文档

如何使用 MongoDB/Mongoose 中的先前值更新字段 [重复]