直接用$group+$match可查重复值,但需注意分组键必须写为"$field"、预过滤null值、大数据量时必须加allowdiskuse:true,否则易漏查或卡死。

直接用 $group + $match 就能查出重复值,但必须注意分组键写法、null 干扰和内存限制,否则要么漏掉真重复,要么查询直接卡死。
查单字段重复:_id 必须写成 "$field"
常见错误是把 _id: "email" 写成字面量字符串,结果所有文档被分到同一个组里。正确写法是带 $ 符号的字段引用。
-
_id必须设为"$email",不是"email"或{ email: "$email" } -
count用{ $sum: 1 },别用{ $count: {} }(仅 MongoDB 5.0+ 支持,旧版会报错) - 字段可能为
null或缺失时,$group会把它们全归入_id: null组——如果业务上不算重复,得提前过滤:{ $match: { email: { $exists: true, $ne: null } } }
示例(查 users 中重复的 email):
db.users.aggregate([
{ $group: { _id: "$email", count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } }
])
查多字段组合重复:_id 必须是对象,不能拼字符串
比如要查 name 和 phone 联合重复,不能用 $concat 拼接——一旦某个字段为 null,整个结果变 null,所有记录挤进同一组。
- 正确写法:
_id: { name: "$name", phone: "$phone" },MongoDB 按字段结构精确比对 - 若字段含空格或大小写不一致,先加
$addFields标准化,例如:{ $addFields: { cleanPhone: { $trim: { input: "$phone" } } } } - 避免用
$toString强转类型,不同字段类型(如NumbervsString)会导致分组不匹配
示例(查 orders 中相同 user_id + product_id 的重复下单):
db.orders.aggregate([
{ $group: { _id: { user_id: "$user_id", product_id: "$product_id" }, count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } }
])
大数据量下必须加 allowDiskUse: true
MongoDB 聚合默认内存上限 100MB,$group 阶段遇到高基数字段(比如千万级用户查 email)会直接报错:Sort exceeded memory limit 或中断执行。
-
allowDiskUse: true是调用aggregate()时的选项,不是 pipeline 阶段——Node.js 驱动里要传在第二个参数位置:collection.aggregate(pipeline, { allowDiskUse: true }) - 分片集群上尤其要加,因为
$group会把所有匹配文档拉到 mongos 节点归并,内存压力更大 - 加了之后查询变慢是正常的,但至少能跑完;若仍超时,优先缩小输入范围,比如加时间条件:
{ $match: { createdAt: { $gte: ISODate("2025-01-01") } } }
想拿到重复项的完整文档?别用 $group
如果目标不是统计重复次数,而是找出所有重复的原始文档(比如要删掉其中几条),$group 会丢数据——它只输出每个分组一条结果。
- 真正要“获取所有重复文档”,应该移除
$group,改用$lookup自关联或两阶段查询:先用聚合找出重复值列表,再用$in查原始文档 - 或者在
$group阶段用$addToSet: "$_id"记录 ID 列表,再用$lookup关联回原集合(但要注意内存和性能) - Spring Data MongoDB 中误用
TypedAggregation.group("field")会导致结果去重,本质是把聚合当成了去重工具,而非查找工具
最容易被忽略的是:分片集群中唯一索引只在单个分片生效,$group 查出来的重复,很可能就是因路由不均导致的“合法重复”。这种场景下,查只是第一步,后续必须检查分片键设计是否合理。











