首页 / 资讯中心 / 文章详情

MongoDB导出导入实战:mongodump/mongoexport等工具详解

MongoDB导出导入实战:mongodump/mongoexport等工具详解 ★ FEATURED ARTICLE
做运维也好做开发也好只要手里的项目用到了 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 / mongorestoremongoexport / 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 --urimongodb://user:pass127.0.0.1:27017/?authSourceadmin --out/data/backup这条命令会把所有数据库连同集合、索引定义一起导出到 --out 指定目录下。备份目录里每个数据库一个子目录每个集合对应一个 .bson 文件和一个可选的 .metadata.json 文件。其中 .metadata.json 里保存了索引定义和集合选项恢复时 mongorestore 会自动重建索引。只备份某个数据库mongodump --urimongodb://127.0.0.1:27017 --dborders --out/data/backup只备份某张集合并且只导出符合条件的部分数据mongodump --urimongodb://127.0.0.1:27017 --dborders --collectionorders_2024 \ --query{createdAt: {$gte: ISODate(2024-01-01T00:00:00Z)}} \ --out/data/backup这里你需要注意 --query 参数的写法。mongodump 会把 --query 当作一个完整的 MongoDB 查询文档传给服务端所以语法上必须符合 MongoDB 查询语法。如果你需要包含日期、ObjectId 这类 BSON 类型直接写字符串是不行的除非工具支持扩展 JSONExtended JSON的写法否则更常见的做法是用时间戳整数代替。我实际项目里有个更稳妥的做法先在 mongosh 里跑一遍查询确认返回结果符合预期再把同一段 JSON 查询放进 --query 参数。比如先执行db.orders.find({createdAt: {$gte: new Date(2024-01-01)}}).limit(1).count()确认数据量没问题再执行 mongodump。这样至少能规避掉“日期格式写错导致导出全量数据”的尴尬。如果数据库上业务写入量很大备份期间数据一直在变化普通 mongodump 出来的结果不是一个一致性的时间点快照。这时候你可以加上 --oplog 参数mongodump --urimongodb://127.0.0.1:27017 --oplog --out/data/backup加了 --oplog 之后mongodump 会额外备份一份 oplog操作日志记录从备份开始到结束期间发生的所有写操作。恢复的时候mongorestore 会把主数据恢复到备份开始瞬间的形态然后回放 oplog 里记录的变化最终得到一个一致性的快照。这个参数在核心生产库做长期备份时非常关键。对于磁盘空间紧张的环境还可以用 --archive 参数把备份输出成单一的文件而不是目录mongodump --urimongodb://127.0.0.1:27017 --archive/data/backup/orders.archive --gzip这种做法在定时备份脚本里非常常见一个文件就是一份完整备份配合 gzip 压缩极大降低存储成本。我这边有一套跑了很久的备份脚本每晚对核心业务库执行这条命令然后保留最近 7 天的备份文件实测下来非常稳定。2.2 mongoexport 导出JSON 和 CSV 的正确打开方式mongoexport 的使用场景是“把数据送出去”所以参数设计也更贴近数据交换的需求。导出成 JSONmongoexport --urimongodb://127.0.0.1:27017/orders \ --collectionorders_2024 \ --out/data/export/orders.json注意这里连接串后面直接挂了数据库名 orders等价于老版本写法中的 --dborders。工具会根据连接串里的数据库名自动定位。导出成 CSVmongoexport --urimongodb://127.0.0.1:27017/orders \ --collectionorders_2024 \ --typecsv \ --fieldsorderId,userId,totalAmount,createdAt \ --out/data/export/orders.csvCSV 导出有一个硬性要求必须显式指定 --fields否则工具不知道应该输出哪些列。而且 CSV 是扁平的如果文档里有嵌套对象或数组导出的结果会非常难看——嵌套对象可能变成字符串数组可能被拼接成奇怪的文本。我踩过一个具体的坑项目里有个“收货地址”字段在 MongoDB 里存的是这样一个嵌套结构{ address: { province: 广东, city: 深圳, street: 科技园路 } }如果直接把 address 加进 --fields 导出 CSV得到的单元格内容是{province: 广东, city: 深圳, street: 科技园路}几乎就是个 JSON 字符串。消费数据的同事还得自己在 Excel 里拆分体验很差。正确的做法是显式指定子字段mongoexport --urimongodb://127.0.0.1:27017/orders \ --collectionorders_2024 \ --typecsv \ --fieldsorderId,userId,address.province,address.city,totalAmount \ --out/data/export/orders.csv这样导出的 CSV 里地址就被拆成了独立的两列。这个技能在处理“导出给外部系统”的场景里非常实用。还有条件导出。mongoexport 也支持 --query用法和 mongodump 类似mongoexport --urimongodb://127.0.0.1:27017/orders \ --collectionorders_2024 \ --typecsv \ --fieldsorderId,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 --urimongodb://user:pass127.0.0.1:27017/?authSourceadmin \ --drop /data/backup这个命令会把 /data/backup 目录下的所有数据库恢复到目标实例上。重点解释 --drop 这个参数——它表示在恢复某个集合之前先把目标集合删掉再导入新的数据。不加 --drop 的时候mongorestore 是在已有集合里“插入”新的文档如果集合里本来就存在相同 _id 的文档导入会直接报错或者跳过。这往往会造成歧义。我的习惯是在做整体迁移覆盖的时候一定带上 --drop在做增量导入、往一个已有库里补数据的时候坚决不带 --drop。带不带完全取决于你的目标是“覆盖”还是“追加”不能凭感觉来。恢复单个库或集合mongorestore --urimongodb://127.0.0.1:27017 \ --nsIncludeorders.orders_2024 \ --drop \ /data/backup/orders/orders_2024.bson这里 --nsInclude 的格式是“数据库名.集合名”支持通配符。如果只想排除某些集合用 --nsExclude。mongorestore 默认会读取 .metadata.json 文件恢复索引。有同学在恢复完数据后发现查询特别慢结果一查发现索引没恢复。实际上如果备份目录里只有 .bson 文件而缺失了 .metadata.json 文件比如手工拷贝、文件丢失mongorestore 就只会导数据不会建索引。更危险的是某些老版本的 mongorestore 在恢复大表时会先导数据再建索引期间写操作的时间会拉长。如果想让恢复过程更快可以先用 --noIndexRestore 把数据快速灌进去再单独用创建索引的脚本去补建索引。数据量在百 GB 级时这个策略尤其有效。3.2 mongoimportJSON/CSV 回到 MongoDBmongoimport 是 mongoexport 的逆操作。把上面导出的 JSON 文件导回一个新的集合mongoimport --urimongodb://127.0.0.1:27017/orders \ --collectionorders_imported \ --file/data/export/orders.json导入 CSV 文件需要确认表头字段也要指定 --typecsvmongoimport --urimongodb://127.0.0.1:27017/orders \ --collectionorders_imported \ --typecsv \ --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 --urimongodb://127.0.0.1:27017/orders \ --collectionorders_imported \ --file/data/export/orders.json \ --numInsertionWorkers4 \ --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 --urimongodb://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 库但连接时没有指定 authSourceadmin导致认证失败。这个错误出现的频率比我预想的高得多。确认工具版本与数据库版本匹配。我遇到过数据库 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 --urimongodb://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 --urimongodb://user:pass127.0.0.1:27017/?authSourceadmin \ --archive/data/backup/orders_${DATE_TAG}.archive --gzip mongorestore --urimongodb://user:pass127.0.0.1:27017/?authSourceadmin \ --archive/data/backup/orders_${DATE_TAG}.archive --gzip --drop这个脚本既可以在源库上做备份也可以在目标环境做恢复验证。注意生产环境验证备份是否有效比备份本身更重要。我见过有同学连续跑了三个月备份结果从没做过恢复演练等到真要恢复时才发现备份文件在第二个月开始就是坏的因为磁盘坏道导致部分文件写不完整。定期恢复演练才是备份系统真正存在的意义。5.2 并行迁移两个大库的注意事项如果一台机器上有两个数据库都需要迁移到新环境理论上你可以同时跑两个 mongodump 进程。但这取决于目标库的负载能力。大量并发读操作会把磁盘 IO 拉高如果源环境还与业务共用一台服务器可能会影响线上接口响应。我的建议是多进程并行只用于“数据量大、链路充足、非业务高峰期”的场景否则宁可排队串行也不要为了省时间把线上机器拖垮。5.3 在目标库开放写入前先做好校验导入完成后建议做一次目标库的快速健康检查mongosh --urimongodb://127.0.0.1:27017 --eval db.orders.countDocuments({createdAt: {$gte: ISODate(2024-06-01)}}) 同时对比源库的相同查询结果。如果数量对不上说明导入过程中存在丢数据或重复数据。对数量只是最基础的校验资金相关的集合一定要校验合计金额、文档数、去重后的用户数这类业务逻辑校验才能真正发现问题。我在一次实际迁移中就是靠“对比每天订单的总额”发现问题的——文档数完全一致但金额总和差了 2 万多元。最后查出来是因为源库中有一个字段名为 totalAmount但少量文档里存的是字符串 100.0而另一些是数字 100.0mongoimport 后类型被统一成了 double精度出现了细微差异。所以不要只对文档数量一定要对业务关键指标做数值聚合校验。写在最后我对 MongoDB 导出导入的几点体会做数据导出导入这件事市面上能搜到的命令用法很多但真正决定方案好坏的不是哪条命令写得漂亮而是你看没看清楚自己的场景。要迁移整个库就规规矩矩用 mongodump / mongorestore别嫌它产物丑要给外部系统送数据就老老实实 mongoexport / mongoimport提前想清楚字段能否被消费方理解。这样想清楚了命令敲起来稳得多。另外我强烈建议每次做有风险的数据迁移前先拿一批样本数据在测试环境完整跑一遍导出导入流程把可能出现的权限、版本、编码、类型问题暴露在正式操作之前。小的代价换取的是大迁移时的从容。数据是我们最不该赌的那部分东西。最后再分享一个小技巧如果你的备份文件需要跨公司或跨环境传递记得在传输前计算一下 MD5 校验和防止网络传输中出现文件损坏。一条命令而已但能在关键时刻避免“备份文件是坏的”这种最让人绝望的问题。数据链路越严谨后面的麻烦就越少。
阅读完成 · 觉得有帮助?
咨询建站