嘿,朋友。看到你准备把关系型数据库的数据搬到 MongoDB,我大概能猜到你现在的处境:可能是老系统跑不动了,也可能是新业务需要灵活的模式,又或者是团队想尝尝 NoSQL 的鲜。
别急,这篇指南不是那种“你好我好大家好”的教科书,而是我看过无数案例后,总结出的一套能直接上手的实战方案。我会把常用的工具掰开揉碎讲清楚,更会把那些让人头秃的常见问题一个个指给你看。咱们目标明确:安全第一,数据不掉,过程不慌。
第一步:先别急着动手,做个“体检”
在把任何数据搬走之前,你得先搞清楚两件事:源数据长什么样,目标 MongoDB 怎么接。很多迁移失败,不是因为工具不行,而是因为没想清楚“关系型”和“文档型”的思维方式根本不一样。
1.1 思维转换:从“表”到“集合”
这是最大的坎。在 MySQL 或 PostgreSQL 里,你习惯的是二维表格,一行一条记录,字段固定。但在 MongoDB 里,数据是JSON 风格的文档,嵌套在文档里的子文档(sub-documents)和数组(arrays)是常态。
举个例子:
MySQL 中的用户地址表:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
email VARCHAR(100)
);
CREATE TABLE addresses (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
street VARCHAR(255),
city VARCHAR(100),
zip_code VARCHAR(20),
FOREIGN KEY (user_id) REFERENCES users(id)
);
如果你直接把这两张表平移进 MongoDB,你会得到两个集合,查询时还得靠 $lookup(类似 SQL 的 JOIN)来关联,这不仅性能差,还违背了 MongoDB 的设计初衷。
迁移后的 MongoDB 文档结构(嵌套模式):
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "张三",
"email": "zhangsan@example.com",
"addresses": [
{
"type": "home",
"street": "中关村大街1号",
"city": "北京",
"zip_code": "100080"
},
{
"type": "office",
"street": "CBD 国贸三期",
"city": "北京",
"zip_code": "100020"
}
]
}
看到区别了吗?把一个用户的所有地址放在一个文档里。这样查询用户信息时,一次读取就全拿到了,不用跨集合关联。当然,这也有代价,比如地址太多会超出 MongoDB 单文档 16MB 的限制,但那是后话,先理解这个核心思想。
1.2 类型映射表:这些坑千万别踩
SQL 和 MongoDB 的字段类型不是一一对应的,强行映射会出大问题。下面这张表是我总结的高频易错点:
| SQL 类型 | 推荐 MongoDB 类型 | 注意事项 |
|---|---|---|
INT / BIGINT |
NumberInt / NumberLong |
MongoDB 的整型分 32 位和 64 位,选对避免溢出 |
DECIMAL / NUMERIC |
Decimal128 |
强烈建议用 Decimal128 存钱,别用 Double,会有精度丢失 |
DATETIME / TIMESTAMP |
Date |
MongoDB 内部存的是 UTC 时间,迁移时要处理好时区 |
BOOLEAN |
Boolean |
直接映射,没问题 |
TEXT / VARCHAR |
String |
注意编码,确保 UTF-8 |
BLOB |
BinData |
存二进制数据,如图片、文件 |
ENUM |
String 或 Number |
迁移时检查值是否在允许范围内 |
PRIMARY KEY |
_id |
如果没有业务主键,MongoDB 会自动生成 ObjectId |
特别提示: 关于 DATETIME,PostgreSQL 的 TIMESTAMPTZ 会自动带时区,而 MySQL 的 DATETIME 不带时区。迁移时要确认你的应用层是如何处理时间的,否则上线后可能出现“凌晨 3 点的数据变成了上午 11 点”这种诡异问题。
第二步:工具大比拼——选哪个?
市面上迁移工具不少,但靠谱的其实就那几类。我按使用场景给你分个类,你对号入座。
2.1 官方与原生工具:最稳的基础
MongoDB Compass(图形界面)
- 适合场景:小规模数据迁移、单表验证、测试环境。
- 优点:免费、直观、自带导入/导出功能,支持 CSV、JSON 格式。
- 缺点:大数据量(百万级以上)会很慢,甚至卡死;不支持复杂的类型转换。
- 我的建议:先用它来测试迁移脚本,确认几条数据能正确落地,再上大部队。
mongoimport / mongoexport(命令行)
- 适合场景:中等规模数据、自动化脚本集成、Linux 服务器环境。
- 优点:速度快、支持 JSON 和 CSV 格式、可通过脚本批量处理。
- 缺点:需要手动处理字段映射,类型转换得自己写脚本(比如把日期字符串转成 Date 对象)。
- 代码示例(假设你已经把 MySQL 数据导出成 JSON):
mongoimport --db my_shop --collection products --file products.json --jsonArray
pg_dump + 自定义脚本(针对 PostgreSQL)
- 适合场景:PostgreSQL 源数据,需要精细控制迁移过程。
- 优点:
pg_dump性能极强,可以并发导出。 - 缺点:得自己写解析逻辑,把 SQL dump 文件转成 MongoDB 能接受的格式。
2.2 第三方 ETL 工具:省心但贵
Talend Open Studio / Pentaho
- 适合场景:企业级复杂迁移,需要从多个源(MySQL、Excel、API)整合数据到 MongoDB。
- 优点:可视化流程设计,内置大量转换组件,错误处理机制完善。
- 缺点:学习曲线陡峭,开源版功能有限,企业版贵。
MuleSoft / Informatica
- 适合场景:大型集团,已有成熟的数据集成平台。
- 优点:功能强大,支持实时监控和数据质量校验。
- 缺点:贵,贵,贵。对于中小型项目性价比不高。
2.3 我私藏的“性价比之王”:写个简单的 Python 脚本
说实话,大多数迁移场景根本不需要上重型 ETL 工具。写一个简单的 Python 脚本,配合 pymongo 和 sqlalchemy,灵活、可控、免费,还能实时看到进度和报错。
下面这个例子展示了如何从 MySQL 读取数据,转换类型,然后写入 MongoDB:
from sqlalchemy import create_engine
from pymongo import MongoClient
from datetime import datetime
import decimal
# 1. 连接源数据库 (MySQL)
mysql_engine = create_engine('mysql+pymysql://user:password@localhost:3306/old_db')
# 2. 连接目标数据库 (MongoDB)
mongo_client = MongoClient('mongodb://localhost:27017/')
db = mongo_client['new_db']
collection = db['users']
# 3. 读取数据 (使用 pandas 方便处理)
import pandas as pd
df = pd.read_sql('SELECT id, name, email, created_at, balance FROM users', mysql_engine)
# 4. 数据转换与写入
batch_size = 1000
docs_to_insert = []
for index, row in df.iterrows():
doc = {
"_id": int(row['id']), # 用 MySQL 的 id 作为 _id,方便对照
"name": str(row['name']),
"email": str(row['email']),
"created_at": row['created_at'].to_pydatetime() if pd.notna(row['created_at']) else None,
# Decimal 类型要特殊处理,MongoDB 支持 Decimal128
"balance": decimal.Decimal(str(row['balance'])) if pd.notna(row['balance']) else None
}
docs_to_insert.append(doc)
# 每 1000 条插入一次,避免内存溢出
if len(docs_to_insert) >= batch_size:
collection.insert_many(docs_to_insert, ordered=False)
print(f"已插入 {len(docs_to_insert)} 条记录...")
docs_to_insert = []
# 插入剩余数据
if docs_to_insert:
collection.insert_many(docs_to_insert, ordered=False)
print("迁移完成!")
这段代码的精髓在于:
ordered=False:即使某条数据出错,也不会中断整个批量插入,其他数据依然能写进去。Decimal128处理:专门针对金额字段,避免浮点数精度问题。- 分批插入:保护内存,同时方便监控进度。
第三步:常见“坑”与解决方案
迁移过程中,90% 的问题都出在数据质量和类型不匹配上。下面这三个问题,我见过太多人踩雷。
3.1 问题一:外键怎么办?数据丢失了!
现象:MySQL 里有 1000 个订单,关联了 500 个用户。迁移到 MongoDB 后,发现只有 400 个用户数据,导致 100 个订单成了“孤儿”。
原因:只迁移了主表,没迁移关联表;或者关联表的数据被过滤掉了。
解决方案:
- 确定模式:先决定是用“嵌套”还是“引用”。如果是嵌套(如前面的例子),在迁移用户时,必须同时查出该用户的所有地址/订单,一起写入。
- 检查孤立记录:写个 SQL 查一下源数据库里是否有“孤儿数据”(即外键指向不存在的记录)。
如果有,决定是清理、标记还是补录。-- 检查 MySQL 中的孤儿订单 SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE u.id IS NULL;
3.2 问题二:日期时间不对,差了 8 小时!
现象:数据迁移后,所有时间都晚了或早了 8 小时。
原因:时区问题。MySQL 默认可能用服务器本地时间,而 MongoDB 内部统一存 UTC 时间。如果你的应用层没有正确处理时区转换,就会出现这种“幽灵时间”。
解决方案:
统一时区:在迁移脚本中,强制将源数据的时间转换为 UTC。
from pytz import timezone import pytz # 假设源数据是北京时间 (Asia/Shanghai) beijing_tz = timezone('Asia/Shanghai') utc_tz = timezone('UTC') # 转换逻辑 local_dt = beijing_tz.localize(datetime(2023, 10, 1, 12, 0, 0)) utc_dt = local_dt.astimezone(utc_tz) # 写入 MongoDB 的 utc_dt验证:迁移完成后,抽查几条数据,对比源数据库和 MongoDB 中的时间戳,确保一致性。
3.3 问题三:重复数据,主键冲突
现象:迁移脚本报错 DuplicateKeyException,或者数据重复了。
原因:迁移脚本重跑了,或者源数据库本身就有重复数据(比如并发写入导致的脏数据)。
解决方案:
- 幂等性设计:在迁移脚本中加入“Upsert”逻辑。MongoDB 的
update_one配合upsert=True可以实现“存在则更新,不存在则插入”。collection.update_one( {"_id": doc["_id"]}, # 用业务主键匹配 {"$set": doc}, # 更新所有字段 upsert=True # 关键:如果不存在就插入 ) - 清洗源数据:在迁移前,先用 SQL 查询源数据库中的重复记录。
决定是保留最新的一条,还是合并。-- 找出 MySQL 中重复的用户 SELECT email, COUNT(*) as count FROM users GROUP BY email HAVING COUNT(*) > 1;
第四步:迁移后的“体检”与回滚预案
数据搬过去不是结束,验证才是。
4.1 数据校验清单
- 记录数对比:
- MySQL:
SELECT COUNT(*) FROM users; - MongoDB:
db.users.countDocuments() - 两者必须相等。如果不等,找出缺失的 ID。
- MySQL:
- 抽样检查:随机抽取 100 条数据,逐字段比对。重点看特殊字符(如引号、换行符)、空值、大数值(金额、ID)。
- 索引验证:检查 MongoDB 中是否建立了必要的索引。关系型数据库的主键、唯一索引,在 MongoDB 中要对应创建。
db.users.createIndex({ "email": 1 }, { unique: true })
4.2 回滚预案:如果迁移失败了怎么办?
这是最重要但最容易被忽略的一点。在开始迁移前,必须确保有回滚能力。
- 保留源库:迁移期间,源 MySQL/PostgreSQL 数据库绝对不能删除,最好保持只读状态,避免新数据写入。
- 停机窗口:如果是生产环境,申请一个短暂的停机窗口,在窗口期内完成最后的增量迁移(只迁移迁移期间新增的数据)。
- 切换开关:应用层代码最好能配置数据源。迁移完成后,先切换到 MongoDB 小流量测试,没问题再全量切换。如果出问题,一键切回 MySQL。
结语:迁移是一场马拉松,不是百米冲刺
朋友,迁移数据库不是换个地方存数据那么简单,它是一次对业务逻辑、数据结构的重新梳理。我会建议你不要一次性全量迁移,先选一个小模块(比如“用户地址”或“商品分类”)做试点,跑通整个流程,验证工具、脚本、校验方法都靠谱了,再扩展到核心业务。
过程中肯定会遇到各种奇怪的问题,别慌,多看日志,多对比源数据和目标数据。记住,数据 integrity(完整性)永远高于速度。
希望这份指南能帮你少走弯路。如果还有其他具体问题,比如特定的数据类型转换,或者 MongoDB 的索引优化,随时来问我。祝你迁移顺利!
