做运维也好,做开发也好,只要手里的项目用到了 MongoDB,迟早都会碰到这种场景:新环境要上线,需要把旧环境的库完整搬过去;或者数据分析团队跟你要一份线上业务数据,说“导出个 Excel 或者 CSV 给我就行”。再或者,你要给某个核心集合做一次周期性快照,方便出问题时候回滚。这些需求,本质上都是在做同一件事——MongoDB 的导出与导入。
这篇文章不打算把官方文档抄一遍,而是把我在实际项目里用 mongodump、mongorestore、mongoexport、mongoimport 这四把常用工具的经验,包括它们的适用场景、完整命令、参数取舍、坑和排查思路,一次性捋清楚。无论你只是临时搬家一个测试库,还是计划做正式环境的数据迁移,这篇文章都能给你一套可以直接照着抄的作业。
1. 动手之前的方案选型:先搞清楚四把工具各管什么
1.1 四把工具的关系,可能和你想象的不一样
很多刚接触 MongoDB 的朋友,容易把 mongodump 和 mongoexport 当成“一个备份、一个导出”,好像两者可以随意替换。实际上,这两组工具的设计目标和产物格式完全不同,选错了,后面会特别难受。
- mongodump / mongorestore:这是一对二进制备份工具,产物是 BSON 格式。BSON 是 MongoDB 内部存储的二进制序列化格式,能够完整保留文档中的所有数据类型,包括 ObjectId、Date、Decimal128、Binary、嵌套文档和数组等。它适合做“完整备份”和“数据库迁移”,还原出来的数据在类型和结构上是 1:1 的。
- mongoexport / mongoimport:这是一对跨系统数据交换工具,产物是 JSON 或 CSV。JSON/CSV 是人类可读的、跨平台的通用格式,适合把数据交给其他系统,或者从其他系统把数据拿回来。但代价是:JSON 只能表达 JSON 标准中的类型,CSV 更是只能表达扁平化的标量字段。
我用一个之前项目里的真实场景解释它们的区别:某项目要把 MongoDB 里的用户订单数据导出来,给另一个用 MySQL 的报表系统做分析。当时我直接用了 mongoexport 导出 CSV。这没有问题,因为报表系统只需要订单号、用户名、金额、时间这几个标量字段。但如果哪次我要把整个库从测试环境迁到生产环境,我绝对会用 mongodump,因为只有它才能保证埋点数据里的 UUID 子文档、经纬度坐标数组、时间戳精度都不发生任何变化。
| 对比项 | mongodump / mongorestore | mongoexport / mongoimport |
|---|---|---|
| 数据格式 | BSON 二进制 | JSON / CSV 文本 |
| 类型保真度 | 完整保留所有 BSON 类型 | 只能保留 JSON 标准类型 |
| 典型场景 | 备份、恢复、整库迁移 | 跨系统数据交换、报表导出 |
| 是否支持部分集合 | 支持,按 --db / --collection 过滤 | 支持 |
| 是否支持条件过滤 | 支持,但要用 --query | 支持,通过 --query |
| 大数据量性能 | 高 | 中低,JSON 解析成本高 |
| 增量备份支持 | 支持 --oplog | 不支持,只能全量条件导出 |
你只需要记住一条经验法则:凡是要回到 MongoDB 的,用 dump/restore;凡是数据要离开 MongoDB 的,用 export/import。
1.2 官方工具之外,别急着放弃命令行
现在 MongoDB 官方有 Compass 这个图形化客户端,许多第三方工具像一些常见的数据库管理工具也集成了 MongoDB 模块,界面上点几下就能导出导入,看着很省事。但我在生产环境里很少完全依赖 GUI 做数据迁移,原因有三个:
第一,GUI 工具在大数据量下容易卡死,几十 GB 的数据导出中途断掉,你连断点续传的能力都没有;第二,GUI 工具的认证配置、SSL 配置封装得太“黑盒”,出了问题不好排查;第三,自动化脚本、定时备份、上线发布流程,都需要命令行工具才能嵌进去。
所以我的建议是:GUI 可以用来快速查看数据、做小规模实验,但正式的数据导出导入场景,请回归命令行。命令行虽然看起来原始,但它稳定、可控、可脚本化,这才是工程化的底座。
1.3 动手前先确认连接信息和版本
在跑任何导出导入命令之前,先花两分钟确认三件事,能省掉后面一大半的麻烦:
工具版本和数据库版本的兼容性。MongoDB 官方工具(Database Tools)的版本要与数据库版本接近,最好是同大版本。比如数据库是 MongoDB 7.0,那么你使用的 mongodump 最好也是 7.x。为什么?因为高版本的工具有时候会读取低版本数据库不支持的内部元数据,低版本工具也未必能理解高版本数据库新引入的索引或类型。一个很常见的现象就是:用太旧的 mongodump 备份新版本的库,恢复时会报索引相关错误。
连接串信息。包括地址、端口、认证库和账号。新版工具都支持 --uri 这种统一的连接串格式,远比以前 --host + --port + --username + --password 一长串参数清晰。
认证权限。导出和导入需要账号具备足够的角色权限。简单说:mongodump / mongoexport 需要 read 或 backup 角色;mongorestore / mongoimport 需要 readWrite 或者 restore 角色。如果账号权限不够,工具不会给你任何误导性错误,只会干脆利落地报认证失败。
2. 全量备份与条件导出:mongodump/mongoexport 实操全解
2.1 mongodump 导出:最稳妥的备份方案
mongodump 的常规用法很简单,一条命令就可以备份整个 MongoDB 实例:
mongodump --uri="mongodb://user:pass@127.0.0.1:27017/?authSource=admin" --out=/data/backup这条命令会把所有数据库连同集合、索引定义一起导出到 --out 指定目录下。备份目录里每个数据库一个子目录,每个集合对应一个 .bson 文件和一个可选的 .metadata.json 文件。其中 .metadata.json 里保存了索引定义和集合选项,恢复时 mongorestore 会自动重建索引。
只备份某个数据库:
mongodump --uri="mongodb://127.0.0.1:27017" --db=orders --out=/data/backup只备份某张集合,并且只导出符合条件的部分数据:
mongodump --uri="mongodb://127.0.0.1:27017" --db=orders --collection=orders_2024 \ --query='{"createdAt": {"$gte": ISODate("2024-01-01T00:00:00Z")}}' \ --out=/data/backup这里你需要注意 --query 参数的写法。mongodump 会把 --query 当作一个完整的 MongoDB 查询文档传给服务端,所以语法上必须符合 MongoDB 查询语法。如果你需要包含日期、ObjectId 这类 BSON 类型,直接写字符串是不行的,除非工具支持扩展 JSON(Extended JSON)的写法,否则更常见的做法是用时间戳整数代替。
我实际项目里有个更稳妥的做法:先在 mongosh 里跑一遍查询,确认返回结果符合预期,再把同一段 JSON 查询放进 --query 参数。比如先执行:
db.orders.find({createdAt: {$gte: new Date("2024-01-01")}}).limit(1).count()确认数据量没问题,再执行 mongodump。这样至少能规避掉“日期格式写错导致导出全量数据”的尴尬。
如果数据库上业务写入量很大,备份期间数据一直在变化,普通 mongodump 出来的结果不是一个一致性的时间点快照。这时候,你可以加上 --oplog 参数:
mongodump --uri="mongodb://127.0.0.1:27017" --oplog --out=/data/backup加了 --oplog 之后,mongodump 会额外备份一份 oplog(操作日志),记录从备份开始到结束期间发生的所有写操作。恢复的时候,mongorestore 会把主数据恢复到备份开始瞬间的形态,然后回放 oplog 里记录的变化,最终得到一个一致性的快照。这个参数在核心生产库做长期备份时非常关键。
对于磁盘空间紧张的环境,还可以用 --archive 参数,把备份输出成单一的文件而不是目录:
mongodump --uri="mongodb://127.0.0.1:27017" --archive=/data/backup/orders.archive --gzip这种做法在定时备份脚本里非常常见,一个文件就是一份完整备份,配合 gzip 压缩,极大降低存储成本。我这边有一套跑了很久的备份脚本,每晚对核心业务库执行这条命令,然后保留最近 7 天的备份文件,实测下来非常稳定。
2.2 mongoexport 导出:JSON 和 CSV 的正确打开方式
mongoexport 的使用场景是“把数据送出去”,所以参数设计也更贴近数据交换的需求。
导出成 JSON:
mongoexport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_2024 \ --out=/data/export/orders.json注意这里连接串后面直接挂了数据库名 orders,等价于老版本写法中的 --db=orders。工具会根据连接串里的数据库名自动定位。
导出成 CSV:
mongoexport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_2024 \ --type=csv \ --fields=orderId,userId,totalAmount,createdAt \ --out=/data/export/orders.csvCSV 导出有一个硬性要求:必须显式指定 --fields,否则工具不知道应该输出哪些列。而且 CSV 是扁平的,如果文档里有嵌套对象或数组,导出的结果会非常难看——嵌套对象可能变成字符串,数组可能被拼接成奇怪的文本。
我踩过一个具体的坑:项目里有个“收货地址”字段,在 MongoDB 里存的是这样一个嵌套结构:
{ "address": { "province": "广东", "city": "深圳", "street": "科技园路" } }如果直接把 address 加进 --fields 导出 CSV,得到的单元格内容是{"province": "广东", "city": "深圳", "street": "科技园路"},几乎就是个 JSON 字符串。消费数据的同事还得自己在 Excel 里拆分,体验很差。正确的做法是显式指定子字段:
mongoexport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_2024 \ --type=csv \ --fields=orderId,userId,address.province,address.city,totalAmount \ --out=/data/export/orders.csv这样导出的 CSV 里,地址就被拆成了独立的两列。这个技能在处理“导出给外部系统”的场景里非常实用。
还有条件导出。mongoexport 也支持 --query,用法和 mongodump 类似:
mongoexport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_2024 \ --type=csv \ --fields=orderId,userId,totalAmount \ --query='{"totalAmount": {"$gte": 1000}}' \ --out=/data/export/large_orders.csv如果 --query 里含有空格或者特殊字符,注意在 bash 里加上单引号包裹,防止被 shell 拆成多个参数。这是很多人在命令行里不小心踩的坑。
2.3 大数据量导出,性能和稳定性的平衡
导出的数据量一大,比如单集合超 5000 万条文档,直接 mongoexport 导 CSV 会非常慢,而且可能把业务库的 IO 打满。这时候有几个操作经验可以分享:
第一个经验:优先考虑用 mongodump 处理完整备份,把 mongoexport 只用于“交差”性质的小批量导出。如果确实需要导出全量大文件给外部系统,可以先 mongodump 备份,然后再在另一台机器上把 BSON 转成 JSON/CSV,避免影响业务库性能。
第二个经验:mongoexport 是单进程连续扫描,导大数据量时尽量避开业务高峰期,给它设置一个合理的 --query 时间窗口,分段导出。比如按月份导,1月的文件、2月的文件各导一份,后续要合并再合并。这既能降低单次导出时长,也能减少对业务库的影响。
第三个经验:导出大 JSON 文件时,注意磁盘空间。JSON 文本体积通常比 BSON 原文件大不少,因为 JSON 要转义特殊字符,而且人类可读的格式本身就是冗余的。我见过一次磁盘被 JSON 导出文件打爆的故障,当时导完才 .bson 的备份文件只有 20GB,而导出的 JSON 达到了 45GB。
3. 数据回填与迁移恢复:mongorestore/mongoimport 实操全解
3.1 mongorestore:让数据回到 MongoDB
有备份就有恢复,mongorestore 就是 mongodump 的逆操作。最基本的用法:
mongorestore --uri="mongodb://user:pass@127.0.0.1:27017/?authSource=admin" \ --drop /data/backup这个命令会把 /data/backup 目录下的所有数据库恢复到目标实例上。重点解释 --drop 这个参数——它表示在恢复某个集合之前,先把目标集合删掉,再导入新的数据。不加 --drop 的时候,mongorestore 是在已有集合里“插入”新的文档;如果集合里本来就存在相同 _id 的文档,导入会直接报错或者跳过。这往往会造成歧义。
我的习惯是:在做整体迁移覆盖的时候,一定带上 --drop;在做增量导入、往一个已有库里补数据的时候,坚决不带 --drop。带不带,完全取决于你的目标是“覆盖”还是“追加”,不能凭感觉来。
恢复单个库或集合:
mongorestore --uri="mongodb://127.0.0.1:27017" \ --nsInclude="orders.orders_2024" \ --drop \ /data/backup/orders/orders_2024.bson这里 --nsInclude 的格式是“数据库名.集合名”,支持通配符。如果只想排除某些集合,用 --nsExclude。
mongorestore 默认会读取 .metadata.json 文件恢复索引。有同学在恢复完数据后,发现查询特别慢,结果一查,发现索引没恢复。实际上,如果备份目录里只有 .bson 文件而缺失了 .metadata.json 文件(比如手工拷贝、文件丢失),mongorestore 就只会导数据,不会建索引。更危险的是某些老版本的 mongorestore 在恢复大表时会先导数据再建索引,期间写操作的时间会拉长。如果想让恢复过程更快,可以先用 --noIndexRestore 把数据快速灌进去,再单独用创建索引的脚本去补建索引。数据量在百 GB 级时,这个策略尤其有效。
3.2 mongoimport:JSON/CSV 回到 MongoDB
mongoimport 是 mongoexport 的逆操作。把上面导出的 JSON 文件导回一个新的集合:
mongoimport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_imported \ --file=/data/export/orders.json导入 CSV 文件,需要确认表头字段,也要指定 --type=csv:
mongoimport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_imported \ --type=csv \ --headerline \ --file=/data/export/orders.csvCSV 文件的每列字段名来自文件表头,所以必须加 --headerline。如果 CSV 没有表头,那么你需要自己用 --fields 指定字段顺序,或者用 --fieldFile 读一个字段列表文件。
还有一个非常常用的参数是 --mode。mongoimport 支持三种模式:
- insert:只插入新文档,遇到 _id 冲突直接报错(默认就是这种)。
- upsert:用导入文档的 _id 去匹配目标集合,存在则更新,不存在则插入。适合用来做数据同步。
- merge:同样的行为类似 upsert,但行为细节上,如果遇到匹配项,会用导入文档替换掉整个已有文档,而不是局部字段更新。
我在实际业务里用得最多的是 upsert。举个例子,某外围系统每天生成一批数据,通过 CSV 输出到文件服务器,我这边用 mongoimport 的 upsert 模式定时把这些数据同步到 MongoDB 的聚合集合里。由于 CSV 里带了 _id 字段,重复导入同一天的增量数据,也不会造成重复文档。
3.3 导入过程中如何控制速度与负载
大数据量导入时,mongoimport 默认是单线程的批量插入,速度很慢,而且它是在一个批内逐条等待写入确认,网络往返开销很大。两个参数可以提高导入速度:
mongoimport --uri="mongodb://127.0.0.1:27017/orders" \ --collection=orders_imported \ --file=/data/export/orders.json \ --numInsertionWorkers=4 \ --writeConcern="{w: 1}"- --numInsertionWorkers:并行执行的 worker 线程数,本质上是同时开多个写入流。这个值不是越大越好,目标实例如果采用的是主从模式,太大的并发会拖垮主节点的写入链路。
- --writeConcern:写入关注级别。默认可能是 w: majority,会造成每批写入都要等多数派节点确认,在大数据量导入时性能损失明显。临时改成 w:1(只要主节点确认即可),导入速度会有非常直观的提升。当然,这只适用于“先灌数据、后续再补一致性修复”的场景,生产环境的正式导入需要权衡。
另外,mongorestore 也有 --numInsertionWorkers 参数,但它还额外支持 --batchSize,用来指定每个批次插入的文档数量。默认 1000 条一批。我通常把这批控制在 1000 到 5000 之间,太大的批虽然单次效率高,但一次失败回滚的数据也更大,内核对内存的占用也更明显。稳定优先。
3.4 迁移中的一致性把握:从全量到增量,再到业务切换
聊一个很多人在做正式迁移时忽略的点。如果只是把测试库搬到另一个测试库,全量 restore 就完事了。但生产库迁移,通常不能接受太长的停机窗口,所以有一个相对通用的三步流程:
- 第一次全量备份恢复:在业务低峰期,先用 mongodump + mongorestore 做一次全量数据迁移,把大部分历史数据先搬过去。
- 增量数据同步:全量迁移过程需要几分钟甚至几十分钟,期间新产生的业务数据怎么办?如果目标环境允许短暂连接源库,可以用 mongodump 配 --oplog 备份新写入,再 restore 过去。更常见的是把迁移导致的数据差控制在一个事务内:选择某个业务低峰的“可写窗口”,在窗口内停止写操作,做一次带 --oplog 的最终增量恢复。
- 业务切换与校验:确认增量恢复完成后,把应用的连接串切到新库,然后做数据校验。校验手段包括对比总文档数、抽样比对关键字段、检查索引数量等。
这里我有一个小技巧:在做全量迁移后的数据校验时,不要只看文档总数,更要看每个集合的索引是否一致。很多迁移事故都是数据量对上了,但索引丢了一半,导致线上检索直接炸掉。所以恢复完成后,建议立刻执行一次:
mongosh --uri="mongodb://127.0.0.1:27017" --eval "db.orders.getIndexes()"对比源库和目标库的索引输出,就能快速发现索引缺失。
4. 常见问题与排查技巧实录
4.1 连接与认证问题
现象:执行 mongodump 或 mongoimport 时报错,信息通常包含Authentication failed或者bad auth authentication failed。
排查顺序:
- 确认账号是否存在,密码是否正确;注意连接串里的特殊字符需要进行 URL 编码,比如密码中包含 @ 符号,必须写成 %40。
- 确认认证库(authSource)是否正确。很多 MongoDB 用户在创建用户时把用户建在 admin 库,但连接时没有指定 authSource=admin,导致认证失败。这个错误出现的频率比我预想的高得多。
- 确认工具版本与数据库版本匹配。我遇到过数据库 MongoDB 7.0、工具还是 4.x 老版本的情况,认证协议和加密方式不匹配,导致连接成功但认证失败。
4.2 导入过程中报错object id specified : null
这种报错常见于 mongoimport 导入 JSON 文件,文件中某个字段被设置为"_id": null,或者 _id 字段缺失。MongoDB 要求 _id 为 ObjectId 类型,如果导入的 JSON 里写了 null,导入就会中断。
解决办法:导入前用脚本处理数据,过滤掉 _id 为 null 的记录,或者给这些记录生成新的 ObjectId。更简单的做法是在 mongoexport 之前,先确认导出数据的完整性。
4.3 日期、Decimal128 精度丢失
mongoexport 导出 JSON 时,日期会变成 ISO 字符串,比如"createdAt": "2024-06-01T12:00:00.000Z"。Decimal128 类型的字段在导出后,如果某些工具不支持,可能会变成 double,丢失精度。解决方法是:明确知道数据要回到 MongoDB,且需要保留精度,就全程用 mongodump/mongorestore;只有面对外部报表系统这类消费方,才用 JSON/CSV。
如果 CSV 里的日期字段需要被 Excel 正确识别,建议导出一个额外的字段作为“本地时间字符串”。比如在 MongoDB 里存储了 UTC 时间,导出时可以用聚合管道生成一个本地时间字符串字段,再导出 CSV。否则 Excel 用户会看到一串难懂的 UTC 时间。
4.4 导入大 CSV 遇到格式错误
现象:mongoimport 导入 CSV 时,遇到某一行格式不对,工具直接终止。这通常是因为 CSV 中的某个字段包含了换行符或者引号,导致解析错位。
解决办法:导入前先检查源文件:
file orders.csv确认文件编码是 UTF-8,不要是 GBK 或 UTF-8 BOM。如果文件里有 BOM 头,第一列字段名可能会被多一个字符污染。处理方式是:
sed -i '1s/^\xEF\xBB\xBF//' orders.csv保证文件以纯 UTF-8 无 BOM 格式保存。如果 CSV 里确实有换行字段,建议用--fields配合--columnsHaveTypes参数,显式说明每一列的类型,减少解析歧义。
4.5 备份文件很大,disk 空间不够
场景:mongodump 导出到一个磁盘,数据写到一半,磁盘满了,备份文件不完整,而且你无法轻易知道它是否完整。
我的经验是,在编写备份脚本时,不要直接往最终目录写,而是先写到一个临时目录,备份成功后把完成标记文件写入,再通过脚本把临时目录移动到最终位置。如果中途磁盘满了,至少你能知道备份没成功,不会误把残缺备份当作有效备份。
另一个建议是:充分利用 --archive 加 --gzip,尽可能压缩体积。压缩后的 BSON 备份体积通常能降到原始体积的 20%-40%,对磁盘空间紧张的环境非常友好。恢复时对应使用:
mongorestore --uri="mongodb://127.0.0.1:27017" \ --archive=/data/backup/orders.archive --gzip注意备份时的压缩方式和恢复时保持一致,如果备份时用了 --gzip,恢复时也要带 --gzip,否则工具会尝试把已压缩的数据当普通 BSON 读取,直接报错。
5. 实操再加速:一些趁手的脚本技巧与日常建议
5.1 把常用备份封装成一个带日期的脚本
我平时习惯把备份逻辑写成一个简单的 bash 脚本,核心就三行:
DATE_TAG=$(date +%Y%m%d_%H%M%S) mongodump --uri="mongodb://user:pass@127.0.0.1:27017/?authSource=admin" \ --archive=/data/backup/orders_${DATE_TAG}.archive --gzip mongorestore --uri="mongodb://user:pass@127.0.0.1:27017/?authSource=admin" \ --archive=/data/backup/orders_${DATE_TAG}.archive --gzip --drop这个脚本既可以在源库上做备份,也可以在目标环境做恢复验证。注意,生产环境验证备份是否有效,比备份本身更重要。我见过有同学连续跑了三个月备份,结果从没做过恢复演练,等到真要恢复时才发现备份文件在第二个月开始就是坏的,因为磁盘坏道导致部分文件写不完整。定期恢复演练,才是备份系统真正存在的意义。
5.2 并行迁移两个大库的注意事项
如果一台机器上有两个数据库,都需要迁移到新环境,理论上你可以同时跑两个 mongodump 进程。但这取决于目标库的负载能力。大量并发读操作会把磁盘 IO 拉高,如果源环境还与业务共用一台服务器,可能会影响线上接口响应。我的建议是:多进程并行只用于“数据量大、链路充足、非业务高峰期”的场景;否则,宁可排队串行,也不要为了省时间把线上机器拖垮。
5.3 在目标库开放写入前,先做好校验
导入完成后,建议做一次目标库的快速健康检查:
mongosh --uri="mongodb://127.0.0.1:27017" --eval " db.orders.countDocuments({createdAt: {$gte: ISODate('2024-06-01')}}) "同时对比源库的相同查询结果。如果数量对不上,说明导入过程中存在丢数据或重复数据。对数量只是最基础的校验,资金相关的集合,一定要校验合计金额、文档数、去重后的用户数,这类业务逻辑校验才能真正发现问题。
我在一次实际迁移中,就是靠“对比每天订单的总额”发现问题的——文档数完全一致,但金额总和差了 2 万多元。最后查出来,是因为源库中有一个字段名为 totalAmount,但少量文档里存的是字符串 "100.0",而另一些是数字 100.0,mongoimport 后类型被统一成了 double,精度出现了细微差异。所以,不要只对文档数量,一定要对业务关键指标做数值聚合校验。
写在最后:我对 MongoDB 导出导入的几点体会
做数据导出导入这件事,市面上能搜到的命令用法很多,但真正决定方案好坏的,不是哪条命令写得漂亮,而是你看没看清楚自己的场景。要迁移整个库,就规规矩矩用 mongodump / mongorestore,别嫌它产物丑;要给外部系统送数据,就老老实实 mongoexport / mongoimport,提前想清楚字段能否被消费方理解。这样想清楚了,命令敲起来稳得多。
另外,我强烈建议每次做有风险的数据迁移前,先拿一批样本数据在测试环境完整跑一遍导出导入流程,把可能出现的权限、版本、编码、类型问题暴露在正式操作之前。小的代价换取的是大迁移时的从容。数据是我们最不该赌的那部分东西。
最后再分享一个小技巧:如果你的备份文件需要跨公司或跨环境传递,记得在传输前计算一下 MD5 校验和,防止网络传输中出现文件损坏。一条命令而已,但能在关键时刻避免“备份文件是坏的”这种最让人绝望的问题。数据链路越严谨,后面的麻烦就越少。