Mongoose(MongoDB)批量插入?

133

Mongoose v3.6+现在支持批量插入吗? 我搜索了几分钟,但是与此查询匹配的任何内容都有几年的历史,并且答案是明确的否定。

编辑:

供将来参考,答案是使用Model.create()create()接受数组作为其第一个参数,因此您可以将要插入的文档传递为数组。

请参见Model.create()文档


请查看之前某个问题的这个答案 - JohnnyHK
谢谢。那就是我发布后最终找到的结果。 - Geuis
@Geuis请将您的编辑作为答案添加并接受它以解决您的问题。 - Filip Dupanović
https://groups.google.com/forum/#!topic/mongoose-orm/IkPmvcd0kds - arcseldon
Mongoose现在支持Model.bulkWrite()Model.insertMany() - Dan Dascalescu
显示剩余2条评论
8个回答

186

Model.create()与Model.collection.insert(): 更快的方法

Model.create()在处理大数据块时,速度非常慢,因此不适用于插入大量数据。 在这种情况下,您应该使用Model.collection.insert,它的性能要好得多。Model.create()根据数据块的大小,甚至可能会崩溃!尝试了一百万个文档,却没有成功。使用Model.collection.insert只需要几秒钟。

Model.collection.insert(docs, options, callback)
  • docs 是要插入的文档数组;
  • options 是一个可选的配置对象 - 详见文档
  • 在所有文档保存成功或出现错误时,将调用callback(err, docs)。成功时,docs是已持久化文档的数组。

正如Mongoose的作者在这里指出的那样,此方法将绕过任何验证程序并直接访问Mongo驱动程序。这是一种你必须做出的权衡,因为你正在处理大量数据,否则你将无法将其插入到数据库中(记住,我们在这里谈论的是数十万个文档)。

一个简单的例子

var Potato = mongoose.model('Potato', PotatoSchema);

var potatoBag = [/* a humongous amount of potato objects */];

Potato.collection.insert(potatoBag, onInsert);

function onInsert(err, docs) {
    if (err) {
        // TODO: handle error
    } else {
        console.info('%d potatoes were successfully stored.', docs.length);
    }
}

更新于2019-06-22:虽然insert()仍然可以正常使用,但它已被弃用,推荐使用insertMany()。参数完全相同,因此您可以将其视为一种即插即用的替代方案,一切都应该能够正常工作(好吧,返回值有点不同,但您可能并没有真正使用它)。

参考文献


1
这个链接已经说得很清楚了,它是关于mongoose-orm的。 - arcseldon
1
请举例说明如何使用Mongoose。 - Stephan Kristyn
18
由于Model.collection直接通过Mongo驱动程序进行操作,因此您将失去所有精彩的mongoose功能,包括验证和钩子函数。这只是需要记住的一点。Model.create会失去钩子函数,但仍然经过验证。如果您想拥有所有这些功能,您必须迭代并使用new MyModel() - Pier-Luc Gendreau
1
@Pier-LucGendreau 你说得完全正确,但一旦开始处理海量数据,这就是你必须做出的权衡。 - Lucio Paiva
1
新读者请注意:“从2.6版本开始更改:insert()返回一个包含操作状态的对象”。没有更多文档。 - Mark Ni
显示剩余9条评论

127

Mongoose 4.4.0现在支持批量插入

Mongoose 4.4.0引入model方法.insertMany()的--真正的--批量插入。它比循环使用.create()或提供数组要快得多。

用法:

var rawDocuments = [/* ... */];

Book.insertMany(rawDocuments)
    .then(function(mongooseDocuments) {
         /* ... */
    })
    .catch(function(err) {
        /* Error handling */
    });

或者

Book.insertMany(rawDocuments, function (err, mongooseDocuments) { /* Your callback function... */ });
你可以在下面链接中跟踪它:

2
目前,此方法不支持选项。 - Amri
谢谢你的回答。你有什么关于rawDocuments解析的想法吗?我已经尝试使用Json对象数组进行解析,但是它只插入了它们的ID。 :( - Ondrej Tokar
4
这个和 bulkWrite 有什么不同?看这里:https://dev59.com/v1kT5IYBdhLWcg3wV-GZ#38743353 - Ondrej Tokar
insertMany 对我不起作用。我得到了一个“致命错误分配失败”的错误。但是如果我使用 collection.insert,它就可以完美地工作。 - John
根据mongoose 5.7.3的说明,insertManycollection.insert慢3-4倍,这可能是因为它将所有文档都返回给客户端。collection.insertpymongo insert_many一样快 - https://stackoverflow.com/questions/58226391/mongoose-vs-pymongo-drivers-write-insertmany-test。 - Yuki
显示剩余2条评论

23

确实,你可以使用Mongoose的"create"方法,它可以包含一个文档数组,参见以下示例:

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

回调函数包含插入的文档。

有时你不知道要插入多少项(像上面的固定参数长度),所以你可以通过循环遍历它们:

var insertedDocs = [];
for (var i=1; i<arguments.length; ++i) {
    insertedDocs.push(arguments[i]);
}

更新:更好的解决方案

一个更好的解决方案是使用Candy.collection.insert()而不是上面例子中使用的Candy.create(),因为它更快(create()在每个项目上都调用Model.save(),因此速度较慢)。

有关更多信息,请参见Mongo文档: http://docs.mongodb.org/manual/reference/method/db.collection.insert/

(感谢arcseldon指出这一点)


根据您的需求,该链接提供了更好的选择。 - arcseldon
你是否是指应该使用 {type:'jellybean'} 而不是 {type:'jelly bean'}?顺便问一下,这些奇怪的类型是什么?它们是 Mongoose API 的一部分吗? - Stephan Kristyn
2
那么命名不好啊,因为在Mongoose中,“type”通常用于表示数据库对象的ADT。 - Stephan Kristyn
2
@sirbenbenji,我已经修改了它,但这也是官方文档中存在的示例。我认为没有必要因此投反对票。 - benske
1
通过访问.collection属性,您可以绕过Mongoose(验证,“pre”方法...)的限制。 - Derek

6
这里介绍了使用insertManysave两种方式保存数据的方法。
1) 使用insertMany以批量方式保存Mongoose文档数组。
/* write mongoose schema model and export this */
var Potato = mongoose.model('Potato', PotatoSchema);

/* write this api in routes directory  */
router.post('/addDocuments', function (req, res) {
    const data = [/* array of object which data need to save in db */];

    Potato.insertMany(data)  
    .then((result) => {
            console.log("result ", result);
            res.status(200).json({'success': 'new documents added!', 'data': result});
    })
    .catch(err => {
            console.error("error ", err);
            res.status(400).json({err});
    });
})

2) Mongoose使用.save()保存文档数组

这些文档将会并行保存。

/* write mongoose schema model and export this */
var Potato = mongoose.model('Potato', PotatoSchema);

/* write this api in routes directory  */
router.post('/addDocuments', function (req, res) {
    const saveData = []
    const data = [/* array of object which data need to save in db */];
    data.map((i) => {
        console.log(i)
        var potato = new Potato(data[i])
        potato.save()
        .then((result) => {
            console.log(result)
            saveData.push(result)
            if (saveData.length === data.length) {
                res.status(200).json({'success': 'new documents added!', 'data': saveData});
            }
        })
        .catch((err) => {
            console.error(err)
            res.status(500).json({err});
        })
    })
})

5

看起来使用mongoose时,当使用find()方法查询文档时,会有超过1000个文档的限制。

Potato.collection.insert(potatoBag, onInsert);

您可以使用:

var bulk = Model.collection.initializeOrderedBulkOp();

async.each(users, function (user, callback) {
    bulk.insert(hash);
}, function (err) {
    var bulkStart = Date.now();
    bulk.execute(function(err, res){
        if (err) console.log (" gameResult.js > err " , err);
        console.log (" gameResult.js > BULK TIME  " , Date.now() - bulkStart );
        console.log (" gameResult.js > BULK INSERT " , res.nInserted)
      });
});

但是在测试10000个文档时,这几乎快了一倍:

function fastInsert(arrOfResults) {
var startTime = Date.now();
    var count = 0;
    var c = Math.round( arrOfResults.length / 990);

    var fakeArr = [];
    fakeArr.length = c;
    var docsSaved = 0

    async.each(fakeArr, function (item, callback) {

            var sliced = arrOfResults.slice(count, count+999);
            sliced.length)
            count = count +999;
            if(sliced.length != 0 ){
                    GameResultModel.collection.insert(sliced, function (err, docs) {
                            docsSaved += docs.ops.length
                            callback();
                    });
            }else {
                    callback()
            }
    }, function (err) {
            console.log (" gameResult.js > BULK INSERT AMOUNT: ", arrOfResults.length, "docsSaved  " , docsSaved, " DIFF TIME:",Date.now() - startTime);
    });
}

1
通过访问.collection属性,您可以绕过Mongoose(验证,“pre”方法...)的限制。 - Derek

4
您可以使用mongoose进行批量插入,这是最高分答案。但是示例无法正常工作,应该如下所示:
/* a humongous amount of potatos */
var potatoBag = [{name:'potato1'}, {name:'potato2'}];

var Potato = mongoose.model('Potato', PotatoSchema);
Potato.collection.insert(potatoBag, onInsert);

function onInsert(err, docs) {
    if (err) {
        // TODO: handle error
    } else {
        console.info('%d potatoes were successfully stored.', docs.length);
    }
}

不要在批量插入中使用模式实例,应该使用普通的映射对象。

第一个回答并没有错,只是它有验证。 - Luca Steeb
1
通过访问.collection属性,您可以绕过Mongoose(验证,“pre”方法...)的限制。 - Derek

4

您可以使用mongoDB shell执行批量插入,只需要将值插入到一个数组中即可。

db.collection.insert([{values},{values},{values},{values}]);

在Mongoose中是否有批量插入的方法? - SUNDARRAJAN K
1
YourModel.collection.insert() - Bill Dami
通过访问.collection属性,您可以绕过Mongoose(验证,“pre”方法...)的限制。 - Derek
这不是mongoose,且collection.insert答案是在本回答几周前给出的,并且详细解释了。 - Dan Dascalescu

0

分享我们项目中的可工作和相关代码:

//documentsArray is the list of sampleCollection objects
sampleCollection.insertMany(documentsArray)  
    .then((res) => {
        console.log("insert sampleCollection result ", res);
    })
    .catch(err => {
        console.log("bulk insert sampleCollection error ", err);
    });

“.insertMany” 的解决方案已经在这个2016年的回答中给出并解释过了。 - Dan Dascalescu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接