$group配合$sum与$match是定位重复值的首选方法,先按字段分组统计次数,再筛选出重复项并降序排列以便优先处理。

直接结论:用 $group + $first(或 $last)是最稳妥的保留一条方式,但必须配合 $sort 控制“哪一条被保留”;盲目用 distinct 只能查不能删,也不返回原始文档。
查重复值:用 $group + $sum + $match
这是定位问题的第一步。重点不是“去重”,而是确认哪些值真正在重复、重复多少次。
-
$group按目标字段(如"$email")分组,用count: { $sum: 1 }统计次数 -
$match筛出count: { $gt: 1 }的分组,避免把唯一值也列出来 - 加
$sort: { count: -1 }能快速看到高频重复项,方便优先处理 - 大数据量时务必加
{ allowDiskUse: true },否则容易报errmsg: "Sort exceeded memory limit"
示例(查 users 中重复的 phone):
db.users.aggregate([
{ $group: { _id: "$phone", count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } },
{ $sort: { count: -1 } }
], { allowDiskUse: true })
保留最新一条:$sort 必须在 $group 前
很多人以为 $first 就是“随便拿一条”,其实它取的是进入 $group 阶段时的**第一条**——而这个“顺序”由上一阶段决定。没 $sort,MongoDB 不保证文档输入 $group 的顺序,结果不可控。
- 想留最新记录:先
$sort: { updatedAt: -1 },再$group+$first: "$$ROOT" - 想留最早记录:改用
$sort: { createdAt: 1 } -
$$ROOT是完整文档,保留所有字段;若只想要部分字段,可在$project后续处理 - 注意:如果
updatedAt字段有null或缺失,排序可能把它们排到前面,建议先$match过滤掉异常值
示例(按 phone 去重,保留 updatedAt 最大的那条):
db.users.aggregate([
{ $sort: { updatedAt: -1 } },
{ $group: { _id: "$phone", doc: { $first: "$$ROOT" } } },
{ $replaceRoot: { newRoot: "$doc" } }
])
删除多余重复数据:别用 remove(),改用 $push + forEach
聚合本身不修改数据,要删必须靠驱动层或 shell 的循环。但直接 remove({ _id: { $in: [...] } }) 很危险——万一数组超长会触发 BSON 限制(16MB),且无法原子性控制。
- 先用
$group收集每组的所有_id,用$push: "$_id"构成数组 - 用
$slice: [1, 999999]踢掉第一个(即保留的那个),剩下全是该删的 -
forEach中对每个dups数组执行deleteMany,比remove更明确语义 - 严格区分大小写:
$push不是$PUSH,addToSet不是addtoset,错一个就语法报错
示例(删掉除最新外所有重复 phone 记录):
db.users.aggregate([
{ $sort: { updatedAt: -1 } },
{ $group: { _id: "$phone", ids: { $push: "$_id" } } },
{ $project: { toDelete: { $slice: ["$ids", 1, { $size: "$ids" }] } } }
]).forEach(doc => {
if (doc.toDelete.length > 0) {
db.users.deleteMany({ _id: { $in: doc.toDelete } });
}
});
distinct 为什么不能替代聚合去重?
db.collection.distinct("field") 看起来最简单,但它只返回一个去重后的值数组,不带任何上下文信息。
- 你不知道每条值对应多少文档,无法判断是否真要删
- 得不到原始文档,没法选“留哪一条”或检查字段完整性
- 不支持多字段联合去重(比如
"name"和"idCard"一起判重),distinct只接受单字段名字符串 - 它底层也是走聚合,但封装太死,遇到复杂逻辑(如按时间取最新)完全无能为力
真正需要“排查+保留+清理”闭环时,$group 是唯一可扩展的路径。那些省事的捷径,往往在业务稍一变化时就卡住。


















