虽然MongoDB的复制集架构极大地保证了数据的可用性,数据丢失的风险变得很低,但这绝不意味着我们可以忽视备份工作。就像给房子买了保险,并不代表不用防火一样,定期备份是守护数据资产的最后一道坚实防线。
为什么要进行MongoDB备份?
备份的核心目的主要有以下几点:
- 防范硬件故障:磁盘损坏、服务器宕机等物理问题可能导致数据不可用。
- 避免人为失误:误删数据、误执行破坏性命令是运维中常见的事故。
- 实现时间点恢复:可以将数据回溯到过去的某个特定状态,用于审计或分析。
- 满足合规要求:许多行业法规要求企业必须保留特定时期的数据快照。
MongoDB备份恢复工具箱
MongoDB提供了几套不同的备份恢复工具,它们各有侧重,适用于不同的场景。
逻辑备份:mongoexport / mongoimport
这对工具主要用于逻辑备份,类似于 MySQL 的 mysqldump,可以将数据导出为人类可读的 JSON 或 CSV 格式文件。
适用场景总结:
- 异构平台迁移:例如,将数据从 MySQL 迁移到 MongoDB,或者反向操作。
- 跨大版本迁移:在不同大版本的 MongoDB 之间进行数据迁移(例如从 2.x 迁移到 3.x),当二进制格式(BSON)不兼容时,JSON格式是一个可靠的备选方案。
需要注意的是,mongoexport 只备份数据本身,不会备份索引、账户信息等其他元数据。
物理备份:mongodump / mongorestore
这是 MongoDB 日常备份恢复中最常用的工具组。它们进行的是物理备份,导出的是二进制的 BSON 格式文件,备份和恢复速度通常更快,并且会包含集合的索引信息。
mongodump 的工作原理是在运行时对数据库进行查询,然后将所有查到的文档写入磁盘。这带来一个特点:它产生的备份不一定是数据库的实时快照。如果在备份过程中持续有数据写入,那么备份文件可能无法完全等同于备份完成那一刻的数据库状态。
mongodump 虽然灵活,但在备份大量数据时可能会对线上服务的性能产生一定影响,并且其导出的数据只能代表一个“时间段”的状态,而非精确的“时间点”。
工具对比:如何选择?
- 格式与可读性:
mongoexport 导出 JSON/CSV,可读性强但体积大;mongodump 导出 BSON,体积小但为二进制,几乎不可读。
- 元数据:
mongodump 会包含索引信息,mongoexport 仅包含数据。
- 版本兼容性:不同版本间的 BSON 格式可能有差异,因此跨版本的
mongodump/mongorestore 需谨慎并检查兼容性。此时,格式通用的 mongoexport/mongoimport 成为一个可行的选择。
- 性能影响:
mongodump 在备份大量数据时对线上业务的影响通常比 mongoexport 要小。
简单来说,日常物理备份推荐使用 mongodump/mongorestore;需要进行跨数据库平台或特定格式的数据交换时,则使用 mongoexport/mongoimport。
备份工具的安装
从 MongoDB 4.4 版本开始,数据库工具(包括 mongoexport, mongodump 等)已经从服务器包中分离,拥有独立的版本号。
下载链接:
https://www.mongodb.com/try/download/shell
在 Rocky Linux 8 上安装示例:
[root@Rocky8 ~]# wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel80-x86_64-100.9.5.rpm
[root@Rocky8 ~]# yum -y install mongodb-database-tools-rhel80-x86_64-100.9.5.rpm
[root@Rocky8 ~]# rpm -ql mongodb-database-tools
/usr/bin/bsondump
/usr/bin/mongodump
/usr/bin/mongoexport
/usr/bin/mongofiles
/usr/bin/mongoimport
/usr/bin/mongorestore
/usr/bin/mongostat
/usr/bin/mongotop
...(其他输出省略)...
深入使用 mongoexport 与 mongoimport
导出工具:mongoexport
mongoexport 一次只能针对一个集合(表)进行导出,不支持整库所有表的批量导出。
官方文档:https://www.mongodb.com/docs/database-tools/mongoexport/
基本参数说明:
$ mongoexport --help
参数说明:
-h # 数据库主机IP
-u # 用户名
-p # 密码
-d # 数据库名
-c # 集合名
-f # 指定要导出的字段(列)
-o # 输出文件名
-q # 数据过滤条件(JSON格式查询)
--type=csv 或 --csv # 指定导出为CSV格式(默认为JSON)
--authenticationDatabase <验证库> # 指定用于身份验证的数据库
操作示例:
-
导出为 JSON 格式:
# 导出 test 数据库的 vast 集合到文件
mongoexport -d test -c vast -o /data/backup/vast.json
# 带条件导出特定字段
mongoexport -d test -c log -f uid,name -q '{"uid":3}' -o vast.json
-
导出为 CSV 格式:
# 必须使用 --type=csv 和 -f 指定字段
mongoexport -d test -c vast2 --type=csv -f uid,name,age,date -o /data/backup/vast2.csv
实际导出过程与结果:
[root@Rocky8 ~]# mongoexport -d test -c vast -o /data/backup/vast.json
2024-06-29T12:20:09.686+0800 connected to: mongodb://localhost/
2024-06-29T12:20:10.688+0800 [###.....................] test.vast 88000/572239 (15.4%)
2024-06-29T12:20:11.690+0800 [########................] test.vast 208000/572239 (36.3%)
2024-06-29T12:20:12.688+0800 [#############...........] test.vast 328000/572239 (57.3%)
2024-06-29T12:20:13.688+0800 [###################.....] test.vast 456000/572239 (79.7%)
2024-06-29T12:20:14.440+0800 [########################] test.vast 572239/572239 (100.0%)
2024-06-29T12:20:14.440+0800 exported 572239 records
[root@Rocky8 ~]# head -n 3 /data/backup/vast.json
{"_id":{"$oid":"667ef3cd8467a66237990681"},"id":0,"name":"dinginx","age":25,"date":{"$date":"2024-06-28T17:33:01.225Z"}}
{"_id":{"$oid":"667ef3cd8467a66237990682"},"id":1,"name":"dinginx","age":25,"date":{"$date":"2024-06-28T17:33:01.244Z"}}
{"_id":{"$oid":"667ef3cd8467a66237990683"},"id":2,"name":"dinginx","age":25,"date":{"$date":"2024-06-28T17:33:01.253Z"}}
导入工具:mongoimport
官方文档:https://www.mongodb.com/docs/database-tools/mongoimport/
基本参数说明:
mongoimport --help
#参数说明:
-h # 数据库主机IP
-u # 用户名
-p # 密码
-d # 数据库名
-c # 集合名
-f # 指定要导入的字段(列),顺序需与文件对应
--type=csv # 指定导入CSV格式文件(默认为JSON)
-j, --numInsertionWorkers=<number> # 并发插入的Worker数量,默认为1,增加可提升导入速度
--headerline # 指明CSV文件第一行是列名,导入时跳过
数据恢复示例:
# 导入JSON格式数据到新集合 vast1
mongoimport -d test -c vast1 /data/backup/vast.json
# 导入带表头的标准CSV文件(使用--headerline自动识别列)
mongoimport -d test -c vast2 --type=csv --headerline /data/backup/vast2.csv
# 导入无表头的CSV文件(必须用-f指定字段名)
mongoimport -d test -c vast3 --type=csv -f uid,name,age,date /data/backup/vast2_no_header.csv
异构平台迁移实战案例
案例一:将 MySQL 数据迁移至 MongoDB
方法一:通过 SELECT ... INTO OUTFILE
# 1. 确保MySQL已开启安全文件写入路径(在my.cnf中配置secure-file-priv)
# 2. 从MySQL导出CSV数据(例如导出zabbix.events表)
mysql -e "SELECT * FROM zabbix.events INTO OUTFILE '/tmp/events.csv' FIELDS TERMINATED BY ',';"
# 3. (可选)获取表的列名,并添加到CSV文件第一行,或使用-f参数指定
mysql -e "SELECT GROUP_CONCAT(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='zabbix' AND TABLE_NAME='events';" > columns.txt
# 4. 导入到MongoDB
# 方法A:如果CSV已包含表头行
mongoimport -d test -c events --type=csv --headerline --file /tmp/events.csv
# 方法B:使用-f手动指定所有列名
mongoimport -d test -c events --type=csv -f eventid,source,object,objectid,clock,value,acknowledged,ns,name,severity --file /tmp/events.csv
方法二:通过 mysqldump 导出文本
# 1. 使用mysqldump导出为制表符分隔的文本
mysqldump hellodb students -T /data/mysql
# 会生成 students.sql(表结构)和 students.txt(数据)
# 2. 将制表符替换为逗号
sed -i.bak 's/\t/,/g' /data/mysql/students.txt
# 3. 导入到MongoDB
mongoimport -d test -c students --type=csv -f stuid,name,age,gender,classid,teacherid /data/mysql/students.txt
案例二:将 MongoDB 数据迁移至 MySQL
思路是先用 mongoexport 将数据导出为 CSV,然后在 MySQL 中通过 LOAD DATA INFILE 加载。
# 1. 从MongoDB导出为CSV(假设已导出为 /tmp/students_info.csv)
[root@rocky8 ~]# head /tmp/students_info.csv
1,Shi Zhongyu,22,M,2,3
2,Shi Potian,22,M,1,7
3,Xie Yanke,53,M,2,16
...
# 2. 在MySQL中创建结构相同的表并导入数据
mysql> USE hellodb;
mysql> CREATE TABLE students_info LIKE students; -- 复制表结构
mysql> LOAD DATA INFILE '/tmp/students_info.csv' INTO TABLE students_info
-> FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';
深入使用 mongodump 与 mongorestore
对于分片集群,备份原理与复制集类似,但有重要区别:
- 分别备份:需要为每个分片(Shard)和配置服务器(Config Server)分别执行备份。
- 一致性要求:要确保所有分片能恢复到同一个精确的时间点,保证分片间数据一致性。
- 暂停均衡器:备份前必须停止集群的平衡器(balancer),防止备份期间数据自动分片导致数据不一致或丢失。
备份工具:mongodump
官方文档:https://www.mongodb.com/docs/database-tools/mongodump/
基本参数说明:
mongodump --help
参数说明:
-h, --host # 数据库主机
-u, --username # 用户名
-p, --password # 密码
-d, --db # 数据库名
-c, --collection # 集合名
-o, --out # 输出目录
-q, --query # 导出数据的过滤条件(JSON格式)
-j, --numParallelCollections # 并行导出的集合数量,默认为4,可加快全库备份速度
--oplog # 备份的同时记录oplog,用于实现“热备份”和基于时间点的恢复(仅复制集模式有效)
--gzip # 在导出时直接进行压缩,节省磁盘空间
备份文件的规律:
- 导出的文件存放在以
-o 参数指定的目录下。
- 每个数据库一个子目录。
- 每个集合对应两个文件:
.bson(数据文件)和 .metadata.json(元数据,包含索引信息等)。
- 如果不指定数据库或集合,则导出所有数据。
恢复工具:mongorestore
官方文档:https://www.mongodb.com/docs/database-tools/mongorestore/
关键参数说明:
mongorestore --help
-h, --host # 数据库主机
-u, --username # 用户名
-p, --password # 密码
-d, --db # 恢复到哪个数据库(可与备份时的库名不同)
-c, --collection # 恢复到哪个集合
--authenticationDatabase # 验证库名
--gzip # 恢复压缩的备份文件
--drop # 恢复前先删除目标集合(**危险!请谨慎使用**)
--oplogReplay # 恢复数据后,重放备份中包含的oplog,使数据更接近备份结束点的状态
--oplogLimit <timestamp> # 重放oplog时,仅重放到指定的时间点(用于恢复误操作)
备份与恢复操作范例
# 1. 全库备份
mkdir -p /data/full
mongodump -o /data/full/
# 查看备份结构
ls /data/full/ # 输出: admin config dinginx test ...
# 2. 备份指定库
mongodump -d dinginx -o /data/dinginx_backup
# 3. 备份指定集合
mongodump -d test -c events -o /backup/events
# 4. 压缩备份(推荐)
mongodump -o /backup/full_gzip --gzip
# 5. 恢复全库备份
mongorestore /backup/full
# 6. 从压缩备份中恢复指定库到新库名
mongorestore -d test1 /backup/full_gzip/test --gzip
# 7. 恢复单个集合(需指定.bson文件路径)
mongorestore -d test -c vast1 --gzip /backup/full_gzip/test/vast.bson.gz
# 8. 覆盖式恢复(先删除已存在集合,再恢复)
mongorestore -d magedu --drop /backup/magedu
高级技巧:利用 Oplog 实现精确时间点恢复
什么是 Oplog?
Oplog(操作日志)是 MongoDB 复制集的核心组件,类似于 MySQL 的 binlog。它记录了所有更改数据库数据的操作(插入、更新、删除、DDL等),是一个固定大小的集合(capped collection),保存在 local 数据库的 oplog.rs 集合中。
Oplog 的关键特性:
- 幂等性:无论重放多少次,结果都相同。这是实现精确恢复的基础。
- 时间窗口:由于大小固定,它只保留最近一段时间内的操作记录。
- 热备份关键:配合
mongodump --oplog 选项,可以捕获备份过程中的数据变化,结合备份文件与 oplog,理论上可以将数据库恢复到备份结束后的任意时间点。
范例:模拟误删除并利用 Oplog 恢复
场景:每天凌晨3点进行全量备份。上午10点,dinginx 数据库下的 vast 表被误删除。需要恢复到误删前的状态。
恢复流程:
- 停止应用写入,防止新数据覆盖 Oplog 时间窗口。
- 准备测试环境。
- 恢复前一天的全量备份。
- 截取全备之后到误删除时间点之间的 Oplog。
- 在测试库上重放 Oplog,将数据恢复到误删前一刻。
- 验证无误后,将恢复出的表导出,再导入生产环境。
详细操作步骤:
-
执行一次包含 Oplog 的完全备份(模拟日常备份):
mkdir -p /data/backup/full/
mongodump --oplog -o /data/backup/full/
备份目录下会生成一个 oplog.bson 文件,记录了备份期间发生的操作。
-
模拟备份后的数据变化和误删除操作:
mongosh
myrepl [direct: primary] dinginx> for (i=0;i<10 ;i++){ db.test.insert ( { "id" :i, "name" : "hang" , "age" : 18, "date" : new Date()})}
myrepl [direct: primary] dinginx> db.test2.insert({name:"wang"})
# 模拟上午10点误删除 vast 表
myrepl [direct: primary] dinginx> db.vast.drop()
true
-
备份当前的 Oplog(至关重要):
mongodump -d local -c oplog.rs -o /data/backup/oplog_current/
-
找到误删除操作的时间戳:
mongosh
myrepl:PRIMARY> use local
myrepl:PRIMARY> db.oplog.rs.find({op:”c”, “o.drop”: “vast”}).pretty()
# 查找输出中的 `ts` 字段,例如:
# ts: Timestamp({ t: 1720706535, i: 1 }) # 记下这个值:1720706535:1
-
准备恢复用的备份文件:
# 用最新的oplog覆盖备份中的旧oplog
cp /data/backup/oplog_current/local/oplog.rs.bson /data/backup/full/oplog.bson
-
执行基于时间点的恢复:
# --oplogReplay: 重放oplog
# --oplogLimit “1720706535:1”: 只重放到误删除发生之前的时间点
# --drop: 恢复前清空目标库(测试环境使用)
mongorestore --oplogReplay --oplogLimit “1720706535:1” --drop /data/backup/full/
-
验证恢复结果:
mongosh
myrepl [direct: primary] dinginx> show tables
# 应该能看到 test, test2, vast 三个表都回来了
myrepl [direct: primary] dinginx> db.vast.countDocuments()
# 应该返回原有数据量(例如110)
通过以上步骤,我们成功地利用 Oplog 将数据库恢复到了误删除发生前的精确状态。这套方法充分展现了 MongoDB 在数据备份与恢复方面的强大能力,是每一位数据库管理员必须掌握的核心运维技能。希望这篇在 云栈社区 分享的详细指南能帮助你构建起更稳固的数据安全防线。