
本文介绍如何使用 MongoDB 聚合管道结合 Node.js 逻辑,将多个同用户、单 code 字段的旧文档(如 {user:'123', code:'abc', uses:[...]})合并为单个新文档(如 {user:'123', code:['abc','def'], uses:[...]}),并安全替换原始数据。
本文介绍如何使用 mongodb 聚合管道结合 node.js 逻辑,将多个同用户、单 code 字段的旧文档(如 `{user:'123', code:'abc', uses:[...]}`)合并为单个新文档(如 `{user:'123', code:['abc','def'], uses:[...]}`),并安全替换原始数据。
在实际开发中,当业务逻辑变更导致数据模型升级(例如将单值字段 code: string 改为数组字段 code: string[]),而历史数据仍保持旧结构时,需对存量文档进行批量重构。本方案不依赖 MongoDB 5.0+ 的 $merge 或 $out(避免覆盖风险或丢失索引),而是采用「聚合 → 拆解 → 删除 → 重建」的安全迁移策略。
✅ 核心思路解析
原聚合查询中使用 _id: '$_id' 会导致按原始文档 _id 分组,每个文档自成一组,因此 $addToSet: '$code' 只收集到单个值;改为 _id: '$user' 后,所有同用户文档被归入同一组,从而正确聚合 code 和 uses 数组。
? 完整可执行代码(Node.js + Mongoose)
// 步骤 1:聚合生成新结构数据
const aggregatedResults = await schema.aggregate([
{ $unwind: '$uses' }, // 展开 uses 数组(若 uses 本身是数组;若为单值对象可省略)
{ $group: {
_id: '$user',
code: { $addToSet: '$code' },
uses: { $addToSet: '$uses' }
}
}
]).toArray();
// 步骤 2:逐组删除旧文档并插入新文档
for (const doc of aggregatedResults) {
// 删除该 user 下所有旧文档
await schema.deleteMany({ user: doc._id });
// 创建并保存新结构文档
await new schema({
user: doc._id,
code: doc.code,
uses: doc.uses
}).save();
}
console.log(`✅ 成功迁移 ${aggregatedResults.length} 个用户的数据`);
⚠️ 关键注意事项
- 事务保障(推荐):若数据库支持(MongoDB 4.0+),应将 deleteMany 与 save() 封装在 session.withTransaction() 中,防止部分失败导致数据不一致;
- 索引兼容性:确保 user 字段已建立索引(如 schema.index({ user: 1 })),否则 deleteMany 性能会急剧下降;
- uses 字段处理:若原始 uses 是对象数组(如 [ { time: ..., ip: ... } ]),$addToSet 要求其为 BSON 可比较类型(建议先 $project 提取关键字段,或改用 $push + $reduce 去重);
- 大数据量优化:超过 10,000 文档时,建议分批处理(如每 100 条 commit 一次),避免内存溢出或超时;
- 备份先行:执行前务必对集合执行 db.collection.copyTo("backup_invites_2024") 或导出快照。
该方法逻辑清晰、控制粒度细,适用于中等规模数据迁移,兼顾正确性与可调试性。










