用migrate-mongo做MongoDB迁移可行,但必须每个up/down脚本独立建连并显式close、操作前判断集合/索引存在性、全程try/catch并throw错误,否则migrations集合会误标成功导致状态脱节。

用 migrate-mongo 可行,但必须手动管连接、加判断、包 try/catch,否则迁移记录和实际数据状态会脱节。
为什么 migrate-mongo 的 up/down 脚本不能共享 MongoClient 实例
它内部不自动重连,也不自动关闭;脚本执行完后连接还挂着,但后续脚本再用这个 client 就可能报 Topology was destroyed。更麻烦的是:如果你在 up.js 里调了 client.close(),那紧接着跑的 down.js 就直接崩。
- 每个迁移脚本(
up.js/down.js)都得自己new MongoClient(uri),用完立刻client.close() -
migrate-mongo-config.js里只导出配置对象(url、databaseName),别导出已初始化的client - 不要在配置里复用全局连接池——
migrate-mongo本身不管理连接生命周期
如何写一个不会重复失败的 up.js
常见错误是假设集合一定存在、索引没建过,结果第二次运行 migrate-mongo up 就卡在 createIndex 报错或 updateMany 找不到集合。
- 操作集合前先查:
await db.listCollections({ name: 'users' }).toArray() - 建索引加
{ background: true },避免阻塞;设unique: false显式声明,别依赖默认值 - 批量更新统一用
updateMany(MongoDB 4.x+),别用老式update(..., { multi: true }) - 所有逻辑包在
try/catch里,出错必须throw new Error('...'),不能只console.error
示例片段:
module.exports = {
async up(db, client) {
try {
const users = db.collection('users');
// 先确认集合存在
const collections = await db.listCollections({ name: 'users' }).toArray();
if (collections.length === 0) throw new Error('collection "users" not found');
await users.updateMany({ avatar: null }, { $set: { avatar: '/default.png' } });
await users.createIndex({ email: 1 }, { background: true });
await client.close();
} catch (err) {
await client.close();
throw err;
}
}
};
怎么发现 migrations 集合被污染了
只要某个脚本里抛了未捕获异常(比如 db.collection().findOne is not a function),migrate-mongo 仍会往 migrations 集合里写一条 status: "success" 的记录——下次 up 就跳过它,但数据根本没改。
- 检查方式:
db.getCollection('migrations').find().sort({ _id: -1 }),看最新几条的status字段是不是"error" - 如果发现
status: "success"但数据没变,大概率是脚本里漏了try/catch或没throw - 手动修复要删掉对应文档,但删之前务必备份业务数据——
migrate-mongo不提供回滚能力
最易被忽略的一点:迁移不是“一次写完就完事”,而是每次部署都要验证连接是否真通、集合是否存在、错误是否真抛出。自动化流程里最容易漏掉的,就是那个 await client.close() 和包裹全部逻辑的 try/catch。


















