说真的,听到要从关系型数据库(不管是 Oracle 还是 MySQL)往 MongoDB 这种文档型数据库迁移,很多人的第一反应是:“这事儿能成吗?” 或者更悲观一点:“完了,我的事务怎么办?”
别慌。我见过太多团队因为对 NoSQL 的误解,导致迁移后数据对不上、性能反而下降,最后不得不回滚。今天这篇文章,我不讲那些枯燥的理论,就结合我带过的好几个真实项目,把从传统 RDBMS 到 MongoDB 迁移过程中的“坑”一个个填平。咱们就像老朋友聊天一样,把这块硬骨头啃下来。
为什么要迁移?先搞清楚你的“动机”
在动手之前,你得先问自己:为什么要迁?
如果是为了读写性能,特别是那种高并发、数据量爆炸的场景,MongoDB 确实能扛。以前我们用 MySQL 做日志存储,每天几千万条,分库分表做到吐,后来迁到 MongoDB,一个集群轻松搞定,查询速度还快了三倍。
但如果是为了schema 灵活变更,比如业务需求一天三变,字段加加减减,MongoDB 的 schema-less 特性简直太香了。之前有个项目,产品天天改需求,MySQL 每次 ALTER TABLE 都要停机维护,迁移到 MongoDB 后,直接往 JSON 里塞字段就行,毫无压力。
不过,如果你的业务强依赖复杂的多表 JOIN 和事务一致性(比如金融核心交易系统),那我得劝你三思。MongoDB 虽然支持 ACID 事务,但性能开销比 MySQL 大得多。这时候迁移可能是在给自己挖坑。
核心思维转换:从“表”到“文档”
这是迁移过程中最大的思维障碍。很多人把 MySQL 表直接“复制粘贴”成 MongoDB 的集合,结果查出来的数据一塌糊涂。
举个例子。假设你有一张 MySQL 表 orders 和一张 users 表,存在外键关联:
-- MySQL 结构
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
很多初学者会这样迁移到 MongoDB:
// ❌ 错误的迁移思路:保留外键引用
db.orders.insertOne({
_id: 1,
user_id: 101, // 依然存着外键
amount: 99.99
});
这样做的问题在于:当你查订单时,还得再查一次用户表。MongoDB 的优势是嵌入式数据模型。你应该把用户信息“嵌入”到订单文档里:
// ✅ 推荐的迁移思路:数据冗余,减少查询
db.orders.insertOne({
_id: 1,
user: {
id: 101,
name: "张三"
},
amount: 99.99
});
这就像搬家,你不是把家具一个个拆了运过去,而是把整个房间直接搬过去。虽然有点冗余,但取用的时候极度方便。当然,这要求你在写入端做好数据同步,避免用户信息改了,订单里的还是旧的。
数据转换工具选型:谁才是真香定律?
工欲善其事,必先利其器。市面上工具不少,我帮你梳理一下主流的几种,以及它们的适用场景。
1. MongoDB Compass 官方工具(适合小数据量、测试环境)
如果你只是迁移几百万条数据,MongoDB 自带的 Compass 导入功能其实够用。支持 JSON、CSV、BSON 等格式。
优点:免费、直观、可视化好。 缺点:大数据量下性能差,容易 OOM(内存溢出)。
我有个朋友曾经用 Compass 导 5000 万条数据,导了一半服务器内存爆了,项目差点黄。所以,千万级以上数据,别用它。
2. Apache NiFi / Kafka Connect(适合实时流式迁移)
如果你的业务要求不停机迁移,那 NiFi 或 Kafka 是神器。它们可以实时捕获 MySQL 的 Binlog 变更,然后实时同步到 MongoDB。
# Kafka Connect JDBC Source Connector 配置示例
{
"name": "mysql-source-connector",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
"tasks.max": "1",
"connection.url": "jdbc:mysql://localhost:3306/mydb",
"topic.prefix": "mysql-",
"mode": "incrementing",
"incrementing.column.name": "id"
}
}
优点:零停机、数据一致性高、支持增量同步。 缺点:架构复杂,运维成本高,需要专业团队支持。
3. 自研 ETL 脚本(Python + PyMongo / Go)
对于大多数中小企业,最稳妥的方案还是自研迁移工具。Python 生态里,pymysql 连 MySQL,pymongo 连 MongoDB,逻辑清晰,可控性最强。
这里我给一个完整的 Python 迁移脚本框架,你可以直接参考:
import pymysql
from pymongo import MongoClient
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def migrate_mysql_to_mongodb():
# 1. 连接 MySQL
mysql_conn = pymysql.connect(
host='localhost',
user='root',
password='password',
database='my_mysql_db',
charset='utf8mb4'
)
# 2. 连接 MongoDB
mongo_client = MongoClient('mongodb://localhost:27017/')
db = mongo_client['my_mongo_db']
collection = db['users']
try:
with mysql_conn.cursor() as cursor:
# 3. 分批读取 MySQL 数据,避免一次性加载进内存
cursor.execute("SELECT * FROM users")
batch_size = 1000
batch = []
while True:
rows = cursor.fetchmany(batch_size)
if not rows:
break
# 4. 数据转换:MySQL 行转 MongoDB 文档
for row in rows:
doc = {
'_id': row[0], # 假设第一列是主键
'name': row[1],
'email': row[2],
'created_at': row[3].isoformat() if row[3] else None
}
# 5. 处理类型转换,比如 MySQL 的 DECIMAL 转 float
batch.append(doc)
# 6. 批量写入 MongoDB
if batch:
collection.insert_many(batch, ordered=False)
logger.info(f"Inserted batch of {len(batch)} records")
batch.clear()
except Exception as e:
logger.error(f"Migration failed: {e}")
raise
finally:
mysql_conn.close()
mongo_client.close()
if __name__ == '__main__':
migrate_mysql_to_mongodb()
优点:完全可控、可处理复杂逻辑、成本低。 缺点:开发成本高,需要自己处理边界情况。
我的建议:如果是生产环境的大规模迁移,自研脚本 + 校验工具是最靠谱的。NiFi 适合有实时需求的场景,Compass 只适合玩票。
常见坑点及解决方案
坑一:数据类型不匹配
MySQL 和 MongoDB 的类型系统差异很大。
- DECIMAL 类型:MySQL 的
DECIMAL(10,2)在 MongoDB 里没有直接对应。如果直接转,可能会变成 float,导致精度丢失。解决方案:在迁移脚本中,将 DECIMAL 转为字符串或整数(乘以 100),存储时保持一致。 - DATETIME vs Date:MySQL 的
DATETIME带有时区,MongoDB 的Date是 UTC 时间。解决方案:迁移时统一转为 UTC,或者在 MongoDB 中存 ISODate 字符串。
坑二:主键冲突
MySQL 的主键是自增整数,MongoDB 默认用 _id 作为主键。如果直接迁移,可能会遇到 _id 已存在的冲突。
解决方案:迁移前清空目标集合,或者在迁移脚本中捕获 DuplicateKeyError,跳过或重新生成 ID。
坑三:字符集乱码
MySQL 常用 utf8mb4,MongoDB 默认也支持 UTF-8,但如果在 Linux 环境下,编码配置不一致,中文可能会变成问号。
解决方案:确保 MongoDB 启动时指定 --utf8 参数,或者在连接字符串中设置 charset=utf8mb4。
坑四:索引设计失效
MySQL 里有复合索引,但 MongoDB 的索引策略不同。比如,MySQL 里 (status, created_at) 的索引,在 MongoDB 里可能因为查询模式不同而需要调整。
解决方案:迁移后,使用 MongoDB 的 explain() 命令分析慢查询,重新设计索引。不要照搬 MySQL 的索引。
数据一致性校验:如何证明你没导错?
这是最容易忽视的一步。很多人以为数据导进去了就完事了,结果上线后发现数据对不上,客户投诉才后悔。
推荐校验方法:
- 行数比对:
SELECT COUNT(*) FROM mysql_table和db.mongo_collection.countDocuments(),两边行数必须一致。 - 哈希校验:对关键字段(如主键、金额、时间戳)计算 MD5 或 SHA256,比对迁移前后的哈希值。
- 抽样对比:随机抽取 100 条数据,人工比对每个字段是否一致。
我有个项目,曾经漏校对了 10 万条数据的金额字段,导致财务对账差了 30 多万,那个教训太深刻了。校验一定要做,而且要严谨。
最后的话
从 Oracle MySQL 到 MongoDB 的迁移,不是一次简单的“复制粘贴”,而是一次数据模型的重新设计。它考验的不仅是技术能力,更是对业务逻辑的理解。
- 工具选型:小数据量用 Compass,大数据量用自研脚本,实时迁移用 NiFi/Kafka。
- 思维转变:从“关系”走向“嵌入”,理解 NoSQL 的设计哲学。
- 校验严谨:数据一致性是生命线,千万别心存侥幸。
迁移过程中,你可能会遇到各种奇奇怪怪的问题,比如驱动版本不兼容、内存不足、网络超时等。这时候,多看日志、多查官方文档、多在社区提问,总能找到答案。
希望这篇指南能帮你避开那些我曾经踩过的坑。如果有具体的技术问题,欢迎随时交流,咱们一起解决。毕竟,数据库迁移这条路,一个人走容易摔跤,大家结伴而行,才能走得更远。
