$group阶段吃CPU是因为它需将所有匹配文档加载到内存中进行哈希分桶、归类累加和去重,不走索引且默认不分片并行,纯依赖CPU计算;输入数据量大、分组基数高、表达式复杂或分片未按分片键聚合时尤为显著。

$group阶段为什么吃CPU
因为$group必须把所有匹配文档拉到内存里,逐个归类、累加、去重——它不走索引,也不分片并行(除非配合$sort和$limit提前裁剪),纯靠CPU做哈希分桶和聚合计算。数据量稍大,就直接卡住线程。
哪些场景会让$group更耗CPU
不是所有$group都一样重,关键看输入规模和分组粒度:
- 没加
$match前置过滤,比如db.logs.aggregate([{$group: {_id: "$level"}}])扫全表百万日志 - 分组字段基数极高(如
_id: "$requestId"),导致哈希桶极多、内存分配频繁 - 聚合字段含复杂表达式,例如
total: {$sum: {$add: ["$a", {$multiply: ["$b", 2]}]}},每行都要实时计算 - 在分片集群中未指定分片键做
$group,mongos被迫收拢全部分片结果再本地归并
怎么验证是不是$group在拖垮CPU
用explain("executionStats")看执行细节,重点关注三项:
-
executionStats.nReturned:输出文档数 —— 如果远小于executionStats.totalDocsExamined,说明$group前已有大量数据进来 -
executionStats.executionTimeMillis占比高,且stage为GROUP的子阶段耗时突出 - 日志里出现
Sort exceeded memory limit或Group stage used too much memory警告
实际能立刻做的优化动作
别改架构,先动管道顺序和投影:
- 把
$match尽可能往前挪,哪怕只筛掉20%数据,$group输入量就少20% - 用
$project提前丢弃无关字段,减少每条文档内存占用(尤其避免带大文本、数组) - 如果只是统计总数,优先用
countDocuments()而不是$group+$sum - 分片环境下,确保
$group的_id字段是分片键或其前缀,让各shard能独立完成部分归并
真正难处理的是“既要细粒度分组,又要全量数据”这种需求——这时候$group吃CPU不是bug,是设计约束。得换思路:预聚合写入、物化视图、或者导出到OLAP引擎。











