精华 向mongodb里插入10万条记录的代码怎么写?
发布于 3个月前 作者 DoubleSpout 1876 次浏览 来自 问答

参考上一篇博客,测试mongodb集群,插入4千万数据的报告:

https://cnodejs.org/topic/5518a873687c387d2f5b2953

起初打算用Node.js去做这个性能 测试,碰了几次壁后,改用Python顺利完成任务,现在回过头来再看这个问题,发现里面似乎有坑。 先放上一段Node.js的插入10万条记录的代码:

var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
var dataList = []
var globalCount = 0
console.time('insert,10w');
MongoClient.connect(url, {
        server: {
            poolSize: 50
        }
    },function(err, db) {
      if(err) throw(err)
      for(var i=0; i<100000; i++){
            db.collection('test1').insert({
                 count:i
            }, function(err, r) {
                if(err) throw(err)
                globalCount++
                if(globalCount>=100000){
                    console.timeEnd('insert,10w');
                    process.exit(1);
                }
          });  
    }  
});

测试结果慢的令人发指,Node.js版本是最新的0.12.2

E:\nodejs>node mongodb_error2.js
insert,10w: 9791ms

我用同样的Python脚本去做:

import pymongo
import json
import datetime,time
import copy
import sys, os

def getTimestampFromDatetime(d=None):
    if d is None:
        d = datetime.datetime.now()
    return time.mktime(d.timetuple())

if __name__ == '__main__':  
    start = getTimestampFromDatetime()
    client = pymongo.MongoClient("localhost", 27017, max_pool_size=50)
    db = client.testdb

    saveData = []
    for i in range(0, 100000):
        saveData.append({
            'count':i
        })

    db.test2.insert(saveData)
    end = getTimestampFromDatetime()
    print('time: {0}s'.format(end-start))

测试结果如下:

E:\python>python save_10w.py
time: 1.0

我在想是不是Node.js代码写的太垃圾,于是我找了Mongodb包的另外一个insertMany方法,但是结果却报错了:

E:\nodejs\node_modules\mongodb\lib\utils.js:97
    process.nextTick(function() { throw err; });
                                        ^
Error
    at Object.<anonymous> (E:\nodejs\node_modules\mongodb\node_modules\mongodb-c
ore\lib\error.js:42:24)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Object.<anonymous> (E:\nodejs\node_modules\mongodb\node_modules\mongodb-core\index.js:2:17)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)

下图是Node.js插入10万条后,数据库的截图

node_error.jpg

下图是Python插入10万条后,数据库的截图

py.jpg

可以看到数据库里都有10万条数据了,两者都成功的插入了,并且大小都一样,求大神指教,这个怎么破?

31 回复

我也测了一下,我这平均在4400左右. Screenshot from 2015-04-07 11:41:51.png Screenshot from 2015-04-07 11:42:03.png

确实很吓人:

var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
MongoClient.connect(url, function(err, db) {
  console.time('insert,10w');
  if (err) throw (err)
    var _collection = db.collection('test1');
  for (var i = 0; i < 100000; i++) {
    _collection.insert({
      count: i
    });
  }
  console.timeEnd('insert,10w');
  process.exit(1);
});

你这两个 , nodejs插入10W次; python只插入了一次, 这能比?

@struCoder 你代码是同步的,这样的时间不准确了。

估计是循环部分的batch优化问题造成的差异,毕竟网络通讯还要时间的,我是猜测的,没代码支撑,胡乱讲的。

我也用Meteor试了一下,结果不忍直视:

if (Meteor.isServer) {
  Meteor.startup(function() {
    // code to run on server at startup

  });
  Meteor.methods({
    'insert': function() {
      console.time('insert 10w');
      for (var i = 0; i < 100000; i++) {
        Test.insert({
          count: i
        });
      }
      console.timeEnd('insert 10w');
      process.exit(1);
    }
  });
}

Screenshot from 2015-04-07 12:03:57.png

@struCoder 同样是插入本地数据库,同样的机器,python竟然比Node.js快几倍,我也是醉了,难道是链接库的问题?

@wuxiaoqian88 不是的,都是循环插入,我之前也跟你一样的想法,后来我看了pymongo的源码,发现也是把list循环起来插入的,和Node.js操作一样,另外Node.js还支持一个insertMany,是支持批量插入,几千条数据少没问题,一旦到几万就报错了。

pymongo 源码,其中docs就是用户插入的list

...
if manipulate:
        def gen():
            db = self.__database
            for doc in docs:
                # Apply user-configured SON manipulators. This order of
                # operations is required for backwards compatibility,
                # see PYTHON-709.
                doc = db._apply_incoming_manipulators(doc, self)
                if '_id' not in doc:
                    doc['_id'] = ObjectId()

                doc = db._apply_incoming_copying_manipulators(doc, self)
                ids.append(doc['_id'])
                yield doc
...

不过刚才试了下,python每次循环操作插入和一次性插入,差距还是挺大的,估计是pymongo底层库做掉了这部分事情,要研究下源码了

@wuxiaoqian88 异步事件的回调是在当前代码运行结束后

@coordcn 都是插入本地的数据库,网络通讯的时间可以忽略了~机器配置不同测试结果是有差异的

@DoubleSpout 那就有可能是代码本身的效率问题,尤其在C与javascript交互的那个界面上,是效率热点。

我现在越来越怀疑node.js的设计哲学有问题了,callback被滥用,也用烂了。

@DoubleSpout 这个不清楚,我刚刚用go分别测了一下 单位(毫秒) 第一个就是直接的insert. 第二个采用批处理 测试结果:

第一种

Screenshot from 2015-04-07 15:16:08.png 第二种

Screenshot from 2015-04-07 15:37:31.png

package main

import "fmt"
import "time"
import "gopkg.in/mgo.v2"

type Count struct {
  I int
}

func main() {
  session, err := mgo.Dial("127.0.0.1:27017")
  if err != nil {
      fmt.Print(err)
  }
  defer session.Close()

  b := session.DB("testdb").C("test1").Bulk() // 第二种
  // c := session.DB("testdb").C("test1") //第一种
  start := time.Now()
  for i := 0; i < 100000; i++ {
      b.Insert(&Count{i}) //第二种
      //c.Insert(&Count{i}) //第一种
  }
  b.Run()// 第二种
  duration := time.Since(start)
  fmt.Printf("insert 10w: %v", duration.Seconds()*1e6/float64(1000))
}

差距还是很大的, 都不是一个级别的,对于数据量大的插入也应该有相应的处理,python那个mongo包估计就 做了处理,而node中可能也有。具体的API我没看,这里依据go中的API进行仅仅的猜测。

@struCoder 确实是这样的,我刚才也试了下python,差距很大,不过Node.js要怎么写代码?

@DoubleSpout 刚刚稍微看了一下文档 现在我插入10W耗时1s
这个和上面我写的go的批处理一样,只不过比go逊色一点 :)

var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
MongoClient.connect(url, function(err, db) {
  if (err) throw (err)

  var col = db.collection("test1");
  var batch = col.initializeUnorderedBulkOp({
    useLegacyOps: true
  });
  console.time('insert,10w');
  for (var i = 0; i < 100000; i++) {
    batch.insert({
      count: i
    });
  }
  batch.execute(function(err) {
    if (err) {
      console.log(err)
    }
    console.timeEnd('insert,10w');
  })
});

Screenshot from 2015-04-07 16:53:38.png

@DoubleSpout
interesting
内部好像掉的bulkWrite, 他的第一个参数就是Array :)

@struCoder 不放回调函数的插入操作不是safe操作,不会等待成功插入反馈,所以速度更快一点

@DoubleSpout 其实你还可以把循环push这个操作踢出去,因为这个也是耗的。 总结就是对于mongodb的写入,8000条插一个和插入8000是两个完全不同的概念,而且常规情况下也是前者,不可能会一条条插8000次的。 by the way:mongodb的2.X驱动对于insertMany做了条数限制,只允许同时批量插入1000条。http://mongodb.github.io/node-mongodb-native/2.0/meta/changes-from-1.0/

这贴也能精华???这是把nodejs黑出翔了啊。看代码,不解释

@DoubleSpout

var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
var dataList = []
var globalCount = 0
console.time('insert,10w');
MongoClient.connect(url, {
  server: {
    poolSize: 50
  }
},function(err, db) {
  if(err) throw(err)

  var listData = []
  for(var i=0; i<100000; i++){
    listData.push({
      count:i
    })
  }
  db.collection('test1').insert(listData,function(err,results){
    if(err){
      console.log(err)
    }else{
      console.log(results)
    }
  });
  console.timeEnd('insert,10w');
});

我这边还要快,但是

{ [MongoError: exceeded maximum write batch size of 1000]
  name: 'MongoError',
  message: 'exceeded maximum write batch size of 1000' }
insert,10w: 413ms

报错了 貌似插不进去吧

@haozxuan 请教insertMany为什么限制只允许同时批量插入1000条?这个限制可以修改吗?

@wmzy 我也在找,目前还没有找到相应的解决方案,你可以尝试用下mongodb的1.X驱动,他仅仅限制16M,没有限制条数。

python使用了一次插入10W行数据,nodejs使用了10w次插入一行数据,能不慢吗???他俩在数据库操作上,都是要直达系统调用,不可能存在性能差距。

我居然看完了

网络传输一次和网络传输 10w 次能比的?

知道总共有10W条了竟然还一条条插入。。

偶然在nodeclub里看到的通过循环查询评论的作者信息的for循环,这应该算是异步的?@alsotang,这样插入10W条记录,虽然不能保证顺序,但是会不会稍微快点?

for (var j = 0; j < 100000; j++) {
      (function () {
        //do someting
      })();
    }

@luoyjx 呃。。。又不是说包个函数函数立即调用就是异步了。。

@alsotang 想错了…失误失误…囧

insert,10w: 5746ms(one by one) v.s. insert,10w: 961ms(bulk insert)

var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://127.0.0.1:27017/testdb';
console.time('insert,10w');
MongoClient.connect(url, {
    server: {
        poolSize: 50
    }
}, function(err, db) {
    var col = db.collection('test2');
    var bulk = col.initializeUnorderedBulkOp();
    for(var i = 0; i < 100000; i++){
        bulk.insert({
            count: i
        });
    }
    bulk.execute();
    console.timeEnd('insert,10w');
    process.exit(1);
});
bulk.execute();
console.timeEnd('insert,10w');
process.exit(1);

@district10 这三步都是异步的吧!估计数据没保存好,就打印时间和退出了吧!

@haozxuan batchinsert是正解,insertmany,insert数组都会报错

@tulayang @xadillax @chemdemo @alsotang
当时测试的时候肯定知道插1次和插1万次的区别,但是insertmany方法和insert方法都会报错,而一条条插入是不会报错的,所以就写了这个示例代码

综上,batchinsert是正确的解决方案,insertmany等其他都不行~

@district10 你这个测试不准确的,要把timeend放在回调函数里

回到顶部