MongoDB按data.uuid去重 删除非最新/缺organizationId重复记录
MongoDB 重复数据清理方案
你之前写的聚合管道存在两个核心问题:
- 执行顺序错误:
$match阶段放在$group之前,此时还未生成count统计字段,count: {$gt:1}的匹配规则完全不生效 - 分组逻辑缺失:分组时仅统计了重复uuid的数量,没有保留组内每条记录的
_id、lastCheckin、organizationId信息,无法判定具体哪些记录需要删除。
按照你的清理规则(同uuid下保留lastCheckin最新的1条,所有缺失organizationId的记录全部删除),可以按以下步骤操作:
1. 提取所有待删除记录的ID
根据你使用的MongoDB版本选择对应聚合语句:
MongoDB 5.0及以上版本(推荐,性能更好)
利用窗口函数直接在分区内计算排名,逻辑更简洁:
const toDeleteIds = db.collection.aggregate([ // 仅匹配存在data.uuid的记录,覆盖唯一约束生效范围 { $match: { "data.uuid": { $exists: true, $ne: null } } }, // 按data.uuid分区,按lastCheckin倒序给组内记录排名,最新的记录rank值为1 { $setWindowFields: { partitionBy: "$data.uuid", sortBy: { "lastCheckin": -1 }, output: { groupRank: { $rank: {} } } }}, // 筛选待删除记录:非组内最新,或缺失organizationId { $match: { $or: [ { groupRank: { $ne: 1 } }, { organizationId: { $exists: false } }, { organizationId: null } ] }}, // 仅返回待删除记录的_id,减少传输开销 { $project: { _id: 1 } } ]).toArray().map(item => item._id)
MongoDB 5.0以下版本兼容写法
不依赖窗口函数,通过分组聚合实现相同逻辑:
const toDeleteIds = db.collection.aggregate([ { $match: { "data.uuid": { $exists: true, $ne: null } } }, // 按uuid分组,收集组内所有记录的关键信息 { $group: { _id: "$data.uuid", records: { $push: { _id: "$_id", lastCheckin: "$lastCheckin", hasOrgId: { $ifNull: [ "$organizationId", false ] } } } }}, // 过滤每个组内需要删除的记录 { $project: { deleteList: { $filter: { input: "$records", cond: { $or: [ { $eq: ["$$this.hasOrgId", false] }, { $ne: ["$$this.lastCheckin", { $max: "$records.lastCheckin" }] } ] } } } }}, // 拆分提取所有待删除ID { $unwind: "$deleteList" }, { $replaceRoot: { newRoot: { _id: "$deleteList._id" } } } ]).toArray().map(item => item._id)
2. 分批执行删除
不要一次性删除大批量数据,避免集合长时间锁库影响线上业务,按固定批次删除即可:
const batchSize = 1000 for (let i = 0; i < toDeleteIds.length; i += batchSize) { const currentBatch = toDeleteIds.slice(i, i + batchSize) db.collection.deleteMany({ _id: { $in: currentBatch } }) print(`进度:${Math.min(i + batchSize, toDeleteIds.length)}/${toDeleteIds.length}`) }
前置校验与注意事项
- 执行删除前务必先在测试环境验证逻辑,可以先把
deleteMany换成find查看待删除记录是否符合预期,避免误删有效数据 - 如果存在同uuid下多条记录
lastCheckin完全一致的极端场景,可以在排序规则里增加次级排序键(比如_id: -1),保证每个uuid分组仅保留1条记录 - 数据清理完成后再创建
data.uuid的唯一索引,创建时建议使用MongoDB默认的在线索引创建能力,避免阻塞业务读写。
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

