MongoDB 6.0中$group性能提升关键在于让上游$match和$sort利用复合索引输出有序流:需$match前置且条件为索引前缀,$sort字段与$_id严格一致并匹配索引方向,索引字段顺序必须支持前缀匹配,否则仍全表扫描。

在MongoDB 6.0中对百万级订单集合按用户ID分组统计总金额时,聚合耗时从8.2秒骤降至140毫秒,关键不是加内存或升配置,而是让$group阶段真正用上索引——这要求你精准控制$match、$sort与索引字段的顺序和方向,且必须避开$group本身不走索引的固有限制。
确认$group能否受益于索引
先执行 db.orders.explain("executionStats").aggregate([{$group: {_id: "$userId", total: {$sum: "$amount"}}}]),检查返回结果中stages数组最底层是否出现"stage": "IXSCAN"。若只有"stage": "COLLSCAN"或"stage": "GROUP",说明$group本身没走索引——【$group阶段永远不直接使用索引】,它依赖上游阶段(尤其是$match和$sort)把数据预处理成有序流,才能触发Sorted Group优化。
真正能走索引的是$match和$sort阶段;只有当它们联合输出按分组键严格有序的文档流时,$group才可跳过内部排序,实现零内存重排。
构建支撑$group的复合索引
方法一:针对「过滤+分组」场景建前缀索引
若聚合形如[{$match: {status: "shipped"}}, {$group: {_id: "$userId", cnt: {$sum: 1}}}],创建索引:db.orders.createIndex({status: 1, userId: 1})。索引前缀status支持$match快速定位,后缀userId保证匹配文档天然按分组键升序排列,$group直接流式归并。
方法二:针对「时间范围+分组」高频查询
例如查「2025年Q3已支付订单按商品类目统计」,聚合为[{$match: {paidAt: {$gte: ISODate("2025-07-01"), $lt: ISODate("2025-10-01")}, status: "paid"}}, {$group: {_id: "$category", revenue: {$sum: "$amount"}}}],则索引必须是{paidAt: 1, status: 1, category: 1}——【paidAt和status顺序不能颠倒,否则$match无法利用前缀匹配】,category放最后才能让输出流按分组键有序。
方法三:避免踩坑的错误索引示例
不要建{userId: 1, status: 1}来服务{$match: {status: "shipped"}, $group: {_id: "$userId"}},因为status非索引前缀,$match会退化为全表扫描;也不要建{userId: -1}单字段索引,它对$group无加速效果,MongoDB 6.0不会反向扫描该索引供分组使用。
重写聚合管道以激活索引红利
第一步:$match必须放在最前端,且条件字段必须是复合索引的连续前缀
第二步:在$match后立即插入$sort,排序字段必须与$group的_id字段完全一致,方向必须匹配索引中该字段的方向
例如索引为{status: 1, userId: 1},则$sort必须写为{$sort: {userId: 1}};若索引是{createdAt: -1, userId: 1},$sort就得是{$sort: {userId: 1}}——注意:createdAt的降序不影响userId升序的可用性,但$sort里绝不能出现createdAt。
第三步:$group紧接$sort之后,_id值必须严格等于$sort字段名,不可嵌套或计算
正确写法:[{$match: {status: "shipped"}}, {$sort: {userId: 1}}, {$group: {_id: "$userId", cnt: {$sum: 1}}}]
错误写法:[{$match: {status: "shipped"}}, {$sort: {userId: 1, amount: -1}}, {$group: {_id: "$userId", ...}}]——多加了amount会导致排序键超出索引覆盖范围,IXSCAN失效。
第四步:删除冗余$project或$addFields,它们会中断索引下推链路
验证索引是否生效
执行带explain的聚合,聚焦看三个字段:executionStats.totalKeysExamined应接近executionStats.nReturned,而非远大于后者;executionStats.executionStages.stage顶层应为"IXSCAN";executionStats.executionStages.inputStage.stage若存在,应为"SORT"或"GROUP",而非"COLLSCAN"。
如果totalKeysExamined是nReturned的10倍以上,说明索引选择错误或查询条件未命中前缀;此时立刻检查$match字段顺序与索引定义是否逐字匹配。

















