直接用 explain("executionstats") 查看真实执行统计,重点关注 executiontimemillis 和 totaldocsexamined;若后者远大于命中数,说明缺索引;plansummary 出现 collscan 即未走索引;$sort/$group 易因内存超限落盘,需前置 $match 并精简字段。

直接看 explain("executionStats"),别猜瓶颈在哪
聚合慢,第一反应不是改代码,而是让 MongoDB 自己告诉你它干了啥。用 explain("executionStats") 是最直接、最可信的起点——它不撒谎,只暴露真实执行路径和资源消耗。
- 必须加
"executionStats"模式,否则默认只返回查询计划结构,看不到关键数字(比如扫描了多少文档、耗时多少毫秒) - 重点盯两个字段:
executionTimeMillis(总耗时)和totalDocsExamined(扫描文档数)。如果后者远大于你$match后实际命中的数量,基本就是缺索引 - 注意:
explain()本身会执行一次查询(除非用"queryPlanner"模式),线上大集合慎用高频调用;建议先在低峰期或副本节点上跑
$match 阶段没走索引?看 planSummary 和 keysExamined
聚合里最常被忽视的性能杀手,是 $match 阶段退化成全表扫描(COLLSCAN)。这在日志或 explain 输出里一目了然。
- 查
planSummary字段:出现COLLSCAN就等于没走索引;出现IXSCAN才算合格 - 对比
keysExamined和docsExamined:理想情况二者应接近;若keysExamined为 0 或极小,而docsExamined很大,说明索引根本没被选中 - 常见坑:复合索引字段顺序错(如查询用
{status:1, userId:1},却建了{userId:1, status:1})、或用了不支持索引的操作符(如$regex开头带^以外的模式)
为什么 $sort 和 $group 总是拖后腿?内存超限就写磁盘
$sort 和 $group 是聚合里最“吃内存”的阶段。MongoDB 默认限制 100MB 内存,超了就会落盘——一落盘,速度直接掉一个数量级。
- 查
executionStats里的spilledDataStorage字段:非空即表示已写临时文件,必须优化 -
$sort前没$match过滤?数据量大时极易触发磁盘排序(尤其 >32MB);$group的分组键太多(如按毫秒级时间戳分组)也会撑爆内存 - 解决方案不是盲目开
allowDiskUse:true,而是优先压缩输入:把$match提到最前,加$project只保留必要字段,再考虑是否真需要allowDiskUse
别只盯着单次聚合,用 $indexStats 和慢日志看全局
单次 explain 能解决眼前问题,但真正拖垮系统的,往往是那些“看起来还行”却高频执行的聚合——它们悄悄吃掉大量 I/O 和 CPU。
- 运行
db.collection.aggregate([{$indexStats:{}}])查看每个索引的真实使用频率,长期accesses.ops为 0 的索引可以删,减少写入开销 - 开启 Profiling:
db.setProfilingLevel(1, 100)(记录 >100ms 查询),再用mloginfo --queries扫描日志,找出 Top N 慢聚合,而不是凭感觉优化 - 特别注意带
$lookup的聚合:即使外表有索引,若没在from集合的localField/foreignField上建对应索引,照样全表扫
真正卡住聚合的,往往不是某个炫技的管道写法,而是索引没对上、$match 没放最前、或者压根不知道 docsExamined 已经到了百万级还在硬扛。解释计划不是可选项,是每次改聚合前必看的诊断报告。










