【问题标题】:How to update a large number of documents in MongoDB most effeciently?如何最高效地更新 MongoDB 中的大量文档?
【发布时间】:2015-11-08 13:18:10
【问题描述】:

我想最有效地更新大量(> 100,000)个文档。

我的第一个天真的方法是在 JS 级别上做,编写脚本 首先获取 _ids,然后遍历 _ids 并通过 _id 调用更新(完整 docs 或 $set 补丁)。

我遇到了内存问题,也将数据分片成最大的块。 500 文档(打开和关闭连接)似乎效果不佳。

那么我该如何在 MongoDB 层面解决这个问题?
最佳做法?

我有 3 个常见用例,通常是维护工作流程:

1.更改属性值的类型,而不更改值。

// before
{
  timestamp : '1446987395'
}

// after
{
  timestamp : 1446987395
}

2。根据现有属性的值添加新属性。

// before
{
  firstname : 'John',
  lastname  : 'Doe'
}

// after
{
  firstname : 'John',
  lastname  : 'Doe',
  name      : 'John Doe'
}

3.只需从文档中添加删除属性。

// before
{
  street    : 'Whatever Ave',
  street_no : '1025'
}

// after
{
  street    : 'Whatever Ave',
  no        : '1025'
}

感谢您的帮助。

【问题讨论】:

    标签: javascript node.js mongodb


    【解决方案1】:

    如果您的 MongoDB 服务器是 2.6 或更高版本,最好利用允许执行批量 update 的写入命令 Bulk API > 操作是服务器顶部的简单抽象,可以轻松构建批量操作。这些批量操作主要有两种形式:

    • 有序批量操作。这些操作按顺序执行所有操作,并在第一次写入错误时出错。
    • 无序批量操作。这些操作并行执行所有操作并聚合所有错误。无序批量操作不保证执行顺序。

    注意,对于早于 2.6 的旧服务器,API 将下转换操作。但是,不可能进行 100% 的下转换,因此可能存在一些无法正确报告正确数字的极端情况。

    对于您的三个常见用例,您可以像这样实现 Bulk API:

    案例1.改变属性值的类型,不改变值:

    var MongoClient = require('mongodb').MongoClient;
    
    MongoClient.connect("mongodb://localhost:27017/test", function(err, db) {
        // Handle error
        if(err) throw err;
    
        // Get the collection and bulk api artefacts
        var col = db.collection('users'),           
            bulk = col.initializeOrderedBulkOp(), // Initialize the Ordered Batch
            counter = 0;        
    
        // Case 1. Change type of value of property, without changing the value.        
        col.find({"timestamp": {"$exists": true, "$type": 2} }).each(function (err, doc) {
    
            var newTimestamp = parseInt(doc.timestamp);
            bulk.find({ "_id": doc._id }).updateOne({
                "$set": { "timestamp": newTimestamp }
            });
    
            counter++;
    
            if (counter % 1000 == 0 ) {
                bulk.execute(function(err, result) {  
                    // re-initialise batch operation           
                    bulk = col.initializeOrderedBulkOp();
                });
            }
        });
    
        if (counter % 1000 != 0 ){
            bulk.execute(function(err, result) {
                // do something with result
                db.close();
            }); 
        } 
    });
    

    案例 2. 根据现有属性的值添加新属性:

    MongoClient.connect("mongodb://localhost:27017/test", function(err, db) {
        // Handle error
        if(err) throw err;
    
        // Get the collection and bulk api artefacts
        var col = db.collection('users'),           
            bulk = col.initializeOrderedBulkOp(), // Initialize the Ordered Batch
            counter = 0;        
    
        // Case 2. Add new property based on value of existing property.        
        col.find({"name": {"$exists": false } }).each(function (err, doc) {
    
            var fullName = doc.firstname + " " doc.lastname;
            bulk.find({ "_id": doc._id }).updateOne({
                "$set": { "name": fullName }
            });
    
            counter++;
    
            if (counter % 1000 == 0 ) {
                bulk.execute(function(err, result) {  
                    // re-initialise batch operation           
                    bulk = col.initializeOrderedBulkOp();
                });
            }
        });
    
        if (counter % 1000 != 0 ){
            bulk.execute(function(err, result) {
                // do something with result
                db.close();
            }); 
        } 
    });
    

    案例 3。 只需从文档中添加删除属性。

    MongoClient.connect("mongodb://localhost:27017/test", function(err, db) {
        // Handle error
        if(err) throw err;
    
        // Get the collection and bulk api artefacts
        var col = db.collection('users'),           
            bulk = col.initializeOrderedBulkOp(), // Initialize the Ordered Batch
            counter = 0;        
    
        // Case 3. Simply adding removing properties from documents.    
        col.find({"street_no": {"$exists": true } }).each(function (err, doc) {
    
            bulk.find({ "_id": doc._id }).updateOne({
                "$set": { "no": doc.street_no },
                "$unset": { "street_no": "" }
            });
    
            counter++;
    
            if (counter % 1000 == 0 ) {
                bulk.execute(function(err, result) {  
                    // re-initialise batch operation           
                    bulk = col.initializeOrderedBulkOp();
                });
            }
        });
    
        if (counter % 1000 != 0 ){
            bulk.execute(function(err, result) {
                // do something with result
                db.close();
            }); 
        } 
    });
    

    【讨论】:

    • 完美,谢谢 chridam,错过了批量 API
    • 加快代码速度的建议:(1)在find查询上使用有限的投影,如果你只需要触摸一个字段,只设置那个字段的投影,这样可以加快文档的传递速度电线上。 (2) 正如@chridam 提到的那样,并行使用 UnorderedBulkOp 更新,所以速度要快得多。
    • 上述代码不适用于少于 1000 的记录数或处理 1000 批次后的“剩余”记录。执行的 'if (counter % 1000 != 0){' 处理在回调更新计数器之前。在 forEach 完成所有记录后,您需要修改代码以处理这些。
    猜你喜欢
    • 2018-08-28
    • 2012-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-01
    • 1970-01-01
    • 2018-07-08
    • 2020-12-11
    相关资源
    最近更新 更多