最可靠查重复文档的方法是$group+$match:先按目标字段分组计数,再筛选count>1的组;必须用$sum:1计数,$match紧接其后,加$push:“$$root”可获取完整重复文档。

直接查重复文档,别用 $group 做筛选主逻辑——它天生是聚合去重的,不是找重复的工具。
用 $group + $match 找出重复字段值
这是最常用也最可靠的方式:先按目标字段分组并计数,再筛出数量大于 1 的组。
- 必须把目标字段放在
$group的_id里,比如查email重复,就写{ _id: "$email" } -
count字段要用{ $sum: 1 },不能写成{ $count: {} }(后者是 MongoDB 5.0+ 新语法,但兼容性弱,尤其在旧驱动或 Spring Data 中易报错) -
$match阶段必须紧接在$group后,且条件为{ count: { $gt: 1 } };写成{ count: 2 }会漏掉出现 3 次及以上的记录 - 如果想顺带看到哪些
_id文档属于同一重复组,得在$group里加docs: { $push: "$$ROOT" },否则只返回字段值和计数
示例(查 users 集合中重复的 phone):
db.users.aggregate([
{ $group: { _id: "$phone", count: { $sum: 1 }, docs: { $push: "$$ROOT" } } },
{ $match: { count: { $gt: 1 } } }
])
用 $lookup 自关联查出完整重复文档
当需要原始文档全量字段(比如导出 Excel 或做人工核对),且不希望结果被 $group 改写结构时,自关联更直观。
- 核心思路:把集合自己
$lookup一遍,匹配相同字段值,再过滤出关联结果数组长度 > 1 的文档 - 注意
as字段名不能和原集合已有字段冲突,否则后续$addFields会覆盖 - 必须加
$unwind和$replaceRoot(或$project)才能还原成单文档结构,否则每条原始文档会对应多条输出 - 性能比
$group方案差,数据量大时慎用;但调试时一眼能看出“哪几条文档互为重复”,排查更直接
示例(查 orders 中重复的 orderNo 并返回全部原始文档):
db.orders.aggregate([
{
$lookup: {
from: "orders",
localField: "orderNo",
foreignField: "orderNo",
as: "duplicates"
}
},
{ $addFields: { duplicateCount: { $size: "$duplicates" } } },
{ $match: { duplicateCount: { $gt: 1 } } },
{ $unwind: "$duplicates" },
{ $replaceRoot: { newRoot: "$duplicates" } }
])
用 distinct() 对比总数快速判断是否存在重复
这不是“查找”,而是“快速探针”——适合上线前校验、CI 脚本或运维巡检。
-
db.collection.countDocuments({})和db.collection.distinct("field").length不相等,就说明该字段存在重复 - 注意
distinct()在大数据集上可能触发内存限制(errmsg: "distinct command failed: Exceeded memory limit"),此时要加allowDiskUse: true - PHP/Java 驱动中调用
distinct()方法时,第二个参数是 filter,传空对象{}表示无条件;别误传null导致语法错误 - 这个方法不返回重复值本身,只告诉你“有”,适合做布尔型断言,不适合定位问题数据
真正要导出或修复重复数据时,$group + $push: "$$ROOT" 是平衡可读性、兼容性和性能的最佳选择;而自关联方案虽然啰嗦,但能避免 $group 后丢失嵌套字段或数组顺序这类隐性陷阱。











