【问题标题】:How can I save multiple documents concurrently in Mongoose/Node.js?如何在 Mongoose/Node.js 中同时保存多个文档?
【发布时间】:2012-05-03 05:40:19
【问题描述】:

目前我使用 save 添加单个文档。假设我有一个文档数组,我希望将它们存储为单个对象。有没有办法通过一个函数调用将它们全部添加,然后在完成后获得一个回调?我可以单独添加所有文档,但管理回调以解决所有问题。

【问题讨论】:

标签: node.js mongodb mongoose


【解决方案1】:

Mongoose 现在支持将多个文档结构传递给Model.create。引用他们的 API 示例,它支持传递数组或可变参数对象列表,最后带有回调:

Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
    if (err) // ...
});

或者

var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
Candy.create(array, function (err, jellybean, snickers) {
    if (err) // ...
});

编辑:正如许多人所指出的,这不会执行真正的批量插入 - 它只是隐藏了您自己多次调用save 的复杂性。下面有答案和 cmets 解释了如何使用实际的 Mongo 驱动来实现批量插入以提高性能。

【讨论】:

  • 注意:这不是 BULK 插入 - 底层 mongoose 实现会遍历所有元素并一一提交。
  • ^ 这非常相关,因为它可能会严重影响那些密集使用它的人的性能。
  • 对 Aaron Heckman 2011 年的回应:并非如此。 Model.create(doc1 [, docN], callback) 在这里有点帮助,但它仍然为你调用 model.save 。如果“更快”是指“绕过所有 mongoose 钩子和验证”,那么您可以下拉到本机驱动程序并直接使用它:Movie.collection.insert(docs, options, callback) github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/…
  • 鉴于所有 cmets 和对此的新答案,我已经修改了我的,以指出围绕批量插入和性能的反对意见。然而,我不得不指出,Hoa 从未将性能作为提出这个问题的原因——他们只是想避免等待多个回调成功。
  • 我想强调的是,如果您要处理大量文档,这不是进行批量插入的最佳方式。请参阅stackoverflow.com/a/24848148/778272,其中包含更好的解释。
【解决方案2】:

Mongoose 4.4 添加了一个名为insertMany的方法

验证文档数组并将它们插入的快捷方式 MongoDB 如果它们都是有效的。这个函数比 .create() 快 因为它只向服务器发送一个操作,而不是每个操作一个 文件。

引用问题#723中的vkarpov15:

权衡是 insertMany() 不会触发预保存挂钩,但它应该具有更好的性能,因为它只对数据库进行 1 次往返,而不是每个文档 1 次。

该方法的签名与create相同:

Model.insertMany([ ... ], (err, docs) => {
  ...
})

或者,通过承诺:

Model.insertMany([ ... ]).then((docs) => {
  ...
}).catch((err) => {
  ...
})

【讨论】:

  • 谢谢。它说如果它们 all 有效,它将插入它们;这是否意味着如果一个人失败了,所有的人都会失败?
  • 这是一个批量操作,但它不是原子的。我不确定 Mongoose 是如何做到的,现在无法测试,但它应该返回成功写入的数量。 MongoDB 的文档中有更多详细信息:docs.mongodb.com/manual/reference/method/…
  • 如果一个失败 insertMany 没有插入任何东西,我已经做了测试
  • 在需要验证重复文档的情况下,插入很多文档可能会很痛苦。如果 _id 已经在新文档中指定,它似乎工作正常;它会为那些抛出重复的错误
【解决方案3】:

Mongoose 尚未实现批量插入(请参阅 issue #723)。

既然您知道要保存的文档数量,您可以这样写:

var total = docArray.length
  , result = []
;

function saveAll(){
  var doc = docArray.pop();

  doc.save(function(err, saved){
    if (err) throw err;//handle error

    result.push(saved[0]);

    if (--total) saveAll();
    else // all saved here
  })
}

saveAll();

当然,这是一个权宜之计,我建议使用某种流控制库(我使用 q,它很棒)。

【讨论】:

  • 你能提供使用q的解决方案吗?
  • 我不认为这是“并发”。在前一个保存完成之前,不会调用每个保存。
  • 是的。例如,一个更并发的方法是触发所有saves,等待所有人调用他们的回调并返回一个结果数组。您可以为此使用 async ,或者使用一些 Promise 接口。
  • 这个条件if (--total)什么时候是假的?
  • 我认为上面的答案很老了。 mongoose 中有一个名为 insertMany() 的方法。检查mongoosejs.com/docs/api.html#model_Model.insertMany
【解决方案4】:

Mongoose 中的批量插入可以使用 .insert() 完成,除非您需要访问中间件。

Model.collection.insert(docs, options, callback)

https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js#L71-91

【讨论】:

  • 对 Aaron Heckman 2011 年的回应:并非如此。 Model.create(doc1 [, docN], callback) 在这里有点帮助,但它仍然为你调用 model.save 。如果“更快”是指“绕过所有 mongoose 钩子和验证”,那么您可以下拉到本机驱动程序并直接使用它:Movie.collection.insert(docs, options, callback) github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/…
  • 我一直看到这个答案,但这并不是真正的“猫鼬”做事方式。这完全绕过了 Mongoose 模型。如果您为 mongoose 模型中的某些字段设置了默认值,它们将被忽略,并且不会插入到数据库中。
  • 如何在猫鼬中使用Model.collection.insert?请举个例子。
  • 我知道这种方法有一些批评者,但如果您要处理大量 文件。这个other answer (http://stackoverflow.com/a/24848148/778272) 解释了为什么它更好并举了一个例子。
  • 谁能建议有哪些选择?
【解决方案5】:

使用async parallel,您的代码将如下所示:

  async.parallel([obj1.save, obj2.save, obj3.save], callback);

由于 Mongoose 中的约定与异步 (err, callback) 中的约定相同,因此您无需将它们包装在自己的回调中,只需将保存调用添加到数组中,当所有操作完成后您将收到回调.

如果您使用 mapLimit,您可以控制要并行保存的文档数量。在本例中,我们并行保存 10 个文档,直到所有项目都成功保存。

async.mapLimit(myArray, 10, function(document, next){
  document.save(next);
}, done);

【讨论】:

  • 有趣 - 你介意用myArray 给出一个真实可用的例子吗?而 myArray 有 1000 万个项目。
【解决方案6】:

我知道这是一个老问题,但我担心这里没有正确正确的答案。大多数答案只是谈论遍历所有文档并单独保存每个文档,如果您有多个文档,这是一个坏主意,并且即使是许多请求中的一个,该过程也会重复。

MongoDB 专门有一个 batchInsert() 调用来插入多个文档,这应该从本机 mongodb 驱动程序中使用。 Mongoose 是基于这个驱动构建的,它不支持批量插入。这可能是有道理的,因为它应该是 MongoDB 的对象文档建模工具。

解决方案:Mongoose 带有原生 MongoDB 驱动程序。您可以通过要求它require('mongoose/node_modules/mongodb') 来使用该驱动程序(对此不太确定,但如果它不起作用,您总是可以再次安装 mongodb npm,但我认为它应该)然后做一个正确的batchInsert

【讨论】:

  • 错了,帕斯卡的回答完全没有抓住重点。需要批量插入的人往往需要它,因为他们想一次性插入 10,000,000 个项目。如果没有批量插入,一个需要几秒钟的操作可能需要几个小时。 Model.create 是一个史诗般的失败,因为它假装是一个批量插入,但实际上它只是一个 for 循环。
  • Mongoose 需要进行一些改造。他们的文档也有很多不足之处。
  • 我认为@Yashua 的问题通过使用底层的mongodb javascript 驱动程序解决了这个问题。
【解决方案7】:

较新版本的 MongoDB 支持批量操作:

var col = db.collection('people');
var batch = col.initializeUnorderedBulkOp();

batch.insert({name: "John"});
batch.insert({name: "Jane"});
batch.insert({name: "Jason"});
batch.insert({name: "Joanne"});

batch.execute(function(err, result) {
    if (err) console.error(err);
    console.log('Inserted ' + result.nInserted + ' row(s).');
}

【讨论】:

    【解决方案8】:

    这是另一种不使用额外库的方法(不包括错误检查)

    function saveAll( callback ){
      var count = 0;
      docs.forEach(function(doc){
          doc.save(function(err){
              count++;
              if( count == docs.length ){
                 callback();
              }
          });
      });
    }
    

    【讨论】:

      【解决方案9】:

      使用insertMany 函数插入多个文档。这只会向服务器发送一个操作,Mongoose 在访问 mongo 服务器之前验证所有文档。默认情况下,Mongoose 按照它们在数组中存在的顺序插入项目。如果您可以不维护任何订单,请设置ordered:false

      重要 - 错误处理:

      ordered:true 验证和错误处理发生在一组中时,意味着如果一个失败,一切都会失败。

      ordered:false 验证和错误处理单独发生时,操作将继续。错误将在错误数组中报告。

      【讨论】:

        【解决方案10】:

        你可以使用mongoose返回的promise save,Promise在mongoose没有全部,但是你可以用这个模块添加特性。

        创建一个模块来增强 mongoose 的承诺。

        var Promise = require("mongoose").Promise;
        
        Promise.all = function(promises) {
          var mainPromise = new Promise();
          if (promises.length == 0) {
            mainPromise.resolve(null, promises);
          }
        
          var pending = 0;
          promises.forEach(function(p, i) {
            pending++;
            p.then(function(val) {
              promises[i] = val;
              if (--pending === 0) {
                mainPromise.resolve(null, promises);
              }
            }, function(err) {
              mainPromise.reject(err);
            });
          });
        
          return mainPromise;
        }
        
        module.exports = Promise;
        

        然后和猫鼬一起使用:

        var Promise = require('./promise')
        
        ...
        
        var tasks = [];
        
        for (var i=0; i < docs.length; i++) {
          tasks.push(docs[i].save());
        }
        
        Promise.all(tasks)
          .then(function(results) {
            console.log(results);
          }, function (err) {
            console.log(err);
          })
        

        【讨论】:

        • Uncaught TypeError: Promise resolver undefined is not a function in Promise.js
        【解决方案11】:

        添加一个名为 mongoHelper.js 的文件

        var MongoClient = require('mongodb').MongoClient;
        
        MongoClient.saveAny = function(data, collection, callback)
        {
            if(data instanceof Array)
            {
                saveRecords(data,collection, callback);
            }
            else
            {
                saveRecord(data,collection, callback);
            }
        }
        
        function saveRecord(data, collection, callback)
        {
            collection.save
            (
                data,
                {w:1},
                function(err, result)
                {
                    if(err)
                        throw new Error(err);
                    callback(result);
                }
            );
        }
        function saveRecords(data, collection, callback)
        {
            save
            (
                data, 
                collection,
                callback
            );
        }
        function save(data, collection, callback)
        {
            collection.save
            (
                data.pop(),
                {w:1},
                function(err, result)
                {
                    if(err)
                    {               
                        throw new Error(err);
                    }
                    if(data.length > 0)
                        save(data, collection, callback);
                    else
                        callback(result);
                }
            );
        }
        
        module.exports = MongoClient;
        

        然后在您的代码更改中,您需要

        var MongoClient = require("./mongoHelper.js");
        

        然后何时保存通话(在您连接并检索集合之后)

        MongoClient.saveAny(data, collection, function(){db.close();});
        

        您可以更改错误处理以满足您的需要,在回调中回传错误等。

        【讨论】:

          【解决方案12】:

          这是一个老问题,但是当我搜索“猫鼬插入文档数组”时,它首先出现在谷歌结果中。

          您可以使用两个选项 model.create() [mongoose] 和 model.collection.insert() [mongodb]。在此处查看有关每个选项的优缺点的更全面讨论:

          Mongoose (mongodb) batch insert?

          【讨论】:

            【解决方案13】:

            这是一个在 Mongoose 中直接使用 MongoDB 的 Model.collection.insert() 的示例。请注意,如果您没有那么多文档,比如少于 100 个文档,则不需要使用 MongoDB 的批量操作 (see this)。

            MongoDB 还支持通过传递一个数组来进行批量插入 文档到 db.collection.insert() 方法。

            var mongoose = require('mongoose');
            
            var userSchema = mongoose.Schema({
              email : { type: String, index: { unique: true } },
              name  : String  
            }); 
            
            var User = mongoose.model('User', userSchema);
            
            
            function saveUsers(users) {
              User.collection.insert(users, function callback(error, insertedDocs) {
                // Here I use KrisKowal's Q (https://github.com/kriskowal/q) to return a promise, 
                // so that the caller of this function can act upon its success or failure
                if (!error)
                  return Q.resolve(insertedDocs);
                else
                  return Q.reject({ error: error });
              });
            }
            
            var users = [{email: 'foo@bar.com', name: 'foo'}, {email: 'baz@bar.com', name: 'baz'}];
            saveUsers(users).then(function() {
              // handle success case here
            })
            .fail(function(error) {
              // handle error case here
            });
            

            【讨论】:

            • 这是哪个猫鼬版本?
            猜你喜欢
            • 1970-01-01
            • 2013-09-08
            • 2021-07-27
            • 2017-09-22
            • 1970-01-01
            • 2016-12-26
            • 2017-10-06
            • 1970-01-01
            • 2015-08-21
            相关资源
            最近更新 更多