本文使用的编程语言是 Node.js,连接 MongoDB 的模块用的是mongoose。但是,本文介绍的方法适用于其他编程语言及其对应的 MongoDB 模块。

错误方法:find()

也许,在遍历 MongoDB 集合时,我们会这样写:

  1. const Promise = require("bluebird");
  2. function findAllMembers() {
  3. return Member.find();
  4. }
  5. async function test() {
  6. const members = await findAllMembers();
  7. let N = 0;
  8. await Promise.mapSeries(members, member => {
  9. N++;
  10. console.log(`name of the ${N}th member: ${member.name}`);
  11. });
  12. console.log(`loop all ${N} members success`);
  13. }
  14. test();

注意,我们使用的是 Bluebird 的mapSeries而非map,members 数组中的元素是一个一个处理的。这样就够了吗?

当 Member 集合中的 document 不多时,比如只有 1000 个时,那确实没有问题。但是当 Member 集合中有 1000 万个 document 时,会发生什么呢?如下:

  1. <--- Last few GCs --->
  2. rt of marking 1770 ms) (average mu = 0.168, current mu = 0.025) finalize [5887:0x43127d0] 33672 ms: Mark-sweep 1398.3 (1425.2) -> 1398.0 (1425.7) MB, 1772.0 / 0.0 ms (+ 0.1 ms in 12 steps since start of marking, biggest step 0.0 ms, walltime since start of marking 1775 ms) (average mu = 0.088, current mu = 0.002) finalize [5887:0x43127d0] 35172 ms: Mark-sweep 1398.5 (1425.7) -> 1398.4 (1428.7) MB, 1496.7 / 0.0 ms (average mu = 0.049, current mu = 0.002) allocation failure scavenge might not succeed
  3. <--- JS stacktrace --->
  4. FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
  5. 1: 0x8c02c0 node::Abort() [node]
  6. 2: 0x8c030c [node]
  7. 3: 0xad15de v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
  8. 4: 0xad1814 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
  9. 5: 0xebe752 [node]
  10. 6: 0xebe858 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [node]
  11. 7: 0xeca982 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
  12. 8: 0xecb2b4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
  13. 9: 0xecba8a v8::internal::Heap::FinalizeIncrementalMarkingIfComplete(v8::internal::GarbageCollectionReason) [node]
  14. 10: 0xecf1b7 v8::internal::IncrementalMarkingJob::Task::RunInternal() [node]
  15. 11: 0xbc1796 v8::internal::CancelableTask::Run() [node]
  16. 12: 0x935018 node::PerIsolatePlatformData::FlushForegroundTasksInternal() [node]
  17. 13: 0x9fccff [node]
  18. 14: 0xa0dbd8 [node]
  19. 15: 0x9fd63b uv_run [node]
  20. 16: 0x8ca6c5 node::Start(v8::Isolate*, node::IsolateData*, int, char const* const*, int, char const* const*) [node]
  21. 17: 0x8c945f node::Start(int, char**) [node]
  22. 18: 0x7f84b6263f45 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6]
  23. 19: 0x885c55 [node]
  24. Aborted (core dumped)

可知,内存不足了。

打印find()返回的 members 数组可知,集合中所有元素都返回了,哪个数组放得下 1000 万个 Object?

正确方法:find().cursor()与 eachAsync()

将整个集合 find()全部返回,这种操作应该避免,正确的方法应该是这样的:

  1. function findAllMembersCursor() {
  2. return Member.find().cursor();
  3. }
  4. async function test() {
  5. const membersCursor = await findAllMembersCursor();
  6. let N = 0;
  7. await membersCursor.eachAsync(member => {
  8. N++;
  9. console.log(`name of the ${N}th member: ${member.name}`);
  10. });
  11. console.log(`loop all ${N} members success`);
  12. }
  13. test();

使用cursor()方法返回 QueryCursor,然后再使用eachAsync()就可以遍历整个集合了,而且不用担心内存不够。

QueryCursor是什么呢?不妨看一下 mongoose 文档:

A QueryCursor is a concurrency primitive for processing query results one document at a time. A QueryCursor fulfills the Node.js streams3 API, in addition to several other mechanisms for loading documents from MongoDB one at a time.

总之,QueryCursor 可以每次从 MongoDB 中取一个 document,这样显然极大地减少了内存使用。

如何测试?

这篇博客介绍的内容很简单,但是也很容易被忽视。如果大家测试一下,印象会更加深刻一些。

测试代码很简单,大家可以查看Fundebug/loop-mongodb-big-collection

我的测试环境是这样的:

  • ubuntu 14.04
  • mongodb 3.2
  • nodejs 10.9.0

1. 使用 Docker 运行 MongoDB

  1. sudo docker run --net=host -d --name mongodb daocloud.io/library/mongo:3.2

2. 使用mgodatagen生成测试数据

使用 mgodatagen,1000 万个 document 可以在 1 分多钟生成!

下载 mgodatagen:https://github.com/feliixx/mgodatagen/releases/download/0.7.3/mgodatagen_linux_x86_64.tar.gz

解压之后,复制到/usr/local/bin 目录即可:

  1. sudo mv mgodatagen /usr/local/bin

mgodatagen 的配置文件mgodatagen-config.json如下:

  1. [
  2. {
  3. "database": "test",
  4. "collection": "members",
  5. "count": 10000000,
  6. "content": {
  7. "name": {
  8. "type": "string",
  9. "minLength": 2,
  10. "maxLength": 8
  11. },
  12. "city": {
  13. "type": "string",
  14. "minLength": 2,
  15. "maxLength": 8
  16. },
  17. "country": {
  18. "type": "string",
  19. "minLength": 2,
  20. "maxLength": 8
  21. },
  22. "company": {
  23. "type": "string",
  24. "minLength": 2,
  25. "maxLength": 8
  26. },
  27. "email": {
  28. "type": "string",
  29. "minLength": 2,
  30. "maxLength": 8
  31. }
  32. }
  33. }
  34. ]

执行mgodatagen -f mgodatagen-config.json命令,即可生成 10000 万测试数据。

  1. mgodatagen -f mgodatagen-config.json
  2. Connecting to mongodb://127.0.0.1:27017
  3. MongoDB server version 3.2.13
  4. collection members: done [====================================================================] 100%
  5. +------------+----------+-----------------+----------------+
  6. | COLLECTION | COUNT | AVG OBJECT SIZE | INDEXES |
  7. +------------+----------+-----------------+----------------+
  8. | members | 10000000 | 108 | _id_ 95368 kB |
  9. +------------+----------+-----------------+----------------+
  10. run finished in 1m12.82s

查看 MongoDB,可知新生成的数据有 0.69GB,其实很小,但是使用 find()方法遍历会报错。

  1. show dbs
  2. local 0.000GB
  3. test 0.690GB

3. 执行测试代码

两种不同遍历方法的代码分别位于test1.jstest2.js

参考

如何高效地遍历 MongoDB 超大集合?的更多相关文章

  1. 如何高效的遍历Map?你常用的不一定是最快的

    微信公众号:大黄奔跑 关注我,可了解更多有趣的面试相关问题. 写在之前 如文章标题所言,遍历Map是开发过程中比较常见的行为,实现的方式也有多种方式,本文带领大家一起看看更加高效的遍历 Map. 『茴 ...

  2. MongoDB固定集合

    固定集合 MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头 ...

  3. MongoDB固定集合(Capped Collections)

    MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素! ...

  4. mongoDB之集合操作

    mongoDB之集合操作 mongoDB中的集合相当于mysql中的表. mongoDB中集合的创建: 第一种方式:不限制集合大小   db.createCollection("集合名称&q ...

  5. MongoDB 固定集合

    MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素! ...

  6. mongoDB 删除集合后,空间不释放

    mongoDB 删除集合后,空间不释放,添加新集合,没有重新利用之前删除集合所空出来的空间,也就是数据库大小只增不减. 方法有: 1.导出导入 dump & restore 2.修复数据库 r ...

  7. C#中遍历各类数据集合的方法总结

    C#中遍历各类数据集合的方法总结: 1.枚举类型 //遍历枚举类型Sample的各个枚举名称 foreach (string sp in Enum.GetNames(typeof(Sample))) ...

  8. 关于mongodb删除集合后磁盘空间不释放的问题

    mongodb删除集合后磁盘空间不释放,只有用db.repairDatabase()去修复才能释放. 但是在修复的过程中如果出现了非正常的mongodb的挂掉,再次启动时启动不了的,需要先修复才可以, ...

  9. mongodb的集合操作

    MongoDB 创建集合 1.手动创建: 语法格式: db.createCollection(name, options) 参数说明: name: 要创建的集合名称 options: 可选参数, 指定 ...

随机推荐

  1. socket 套接字服务器端和客户端发送信息

    import socket import threading host='' port=6889 def cilenThred(conn,addr): print("成功接受客户端{}的连接 ...

  2. idea导入maven项目,找不到jar包,出现红色波浪线【转】

    参考链接 点击跳转

  3. 安卓开发学习笔记(三):Android Stuidio无法引用Intent来创建对象,出现cannot resolve xxx

    笔者在进行安卓开发时,发现自己的代码语法完全没有问题.尤其是创建intent对象的时候,语法完全是正确的,但是Android Stuidio却显示报错,Intent类显示为红色,如图所示: 代码如下所 ...

  4. 如何利用GitHub搜索敏感信息

    如何利用GitHub搜索敏感信息 背景: 最近总是能听到同事说在GitHub上搜到某个敏感信息,然后利用该信息成功的检测并发现某个漏洞,最后提交到对应的SRC(安全应急响应中心)换点money.顿时心 ...

  5. [Swift]LeetCode621. 任务调度器 | Task Scheduler

    Given a char array representing tasks CPU need to do. It contains capital letters A to Z where diffe ...

  6. PHP算法之冒泡排序

    //冒泡排序 //①思路,先比较出第一次,找一个最大的值,排到最后; //②重复count遍之后,就能得到排序; //③优化,每一次循环之后不需要再次全部重复; $array = [11,5,4,58 ...

  7. Android device debug (adb) by Charge Only mode

    Android device debug by Charge Only mode Method 1 Connect devices to computer and execute lsusb Find ...

  8. 死磕 java集合之TreeMap源码分析(四)-内含彩蛋

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 二叉树的遍历 我们知道二叉查找树的遍历有前序遍历.中序遍历.后序遍历. (1)前序遍历,先遍历 ...

  9. solr索引报错(java.lang.OutOfMemoryError:GC overhead limit exceeded)

    配置文件修改如下: <dataSource driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3 ...

  10. 使用ML.NET实现猜动画片台词

    前面几篇主要内容出自微软官方,经我特意修改的案例的文章: 使用ML.NET实现情感分析[新手篇] 使用ML.NET预测纽约出租车费 .NET Core玩转机器学习 使用ML.NET实现情感分析[新手篇 ...