聚合超时主因是客户端未设maxTimeMS且服务端未开profiling导致慢查询隐蔽;需显式设置maxTimeMS、统一副本集profiling级别、为$lookup建立匹配复合索引。

聚合超时八成不是服务器扛不住,而是客户端没设 maxTimeMS + 服务端没开 profiling 导致慢查询藏得深。
聚合没加 maxTimeMS,服务端无限执行
Node.js 驱动默认不给聚合加服务端超时,一旦遇到 COLLSCAN 或未优化的 $lookup,mongod 就一直跑,直到 socketTimeoutMS(默认 0,即无限)或客户端主动断连。这不是“超时”,是“卡死”。
- 必须显式传
maxTimeMS:比如collection.aggregate(pipeline).maxTimeMS(5000) -
maxTimeMS是服务端强制中止时间,单位毫秒,和socketTimeoutMS无关 - 注意:它只限制聚合阶段执行时间,不包括网络传输、结果序列化等环节
- 生产环境建议设为 3000–10000,具体看业务容忍度;低于 1000 容易误杀正常聚合
副本集里聚合慢查询被掩盖,db.currentOp() 看不到全貌
在副本集上执行 db.currentOp() 默认只返回 primary 上的活跃操作,secondary 不参与聚合执行,但 slowms 阈值若没统一配置,secondary 的慢操作根本不会进 profile。
- 先确认所有节点都开了 profiling:
db.setProfilingLevel(1, { slowms: 500 }) - 查 profile 时指定
ns过滤聚合所在集合:db.system.profile.find({ ns: "mydb.mycollection", millis: { $gt: 500 } }).sort({ ts: -1 }) - 重点关注
planSummary字段:出现COLLSCAN或IXSCAN后docsExamined远大于nReturned,说明索引没用好 - 别信
explain("queryPlanner"),要用explain("executionStats")看真实扫描量
pipeline 里嵌套 $lookup 没走索引,连接池悄悄被占满
一个没建对索引的 $lookup,会让聚合在 secondary 上也拉取大量数据(即使你只读 primary),而驱动默认的 poolSize 是全局共享的——10 个连接里只要 2 个卡在 $lookup,其余请求就开始排队。
- $lookup 的
from集合必须在localField和foreignField上有复合索引,顺序要匹配:比如{ localField: 1, foreignField: 1 } - 避免在 $lookup 后再做
$match过滤大量文档;应把过滤条件尽量前置,或改用$lookup的pipeline选项下推 - 检查驱动版本:v4.15+ 才支持
$lookuppipeline 中的$expr下推,老版本只能全量拉再内存过滤 - 如果聚合涉及跨分片(sharded cluster),$lookup 会退化为广播查询,此时必须确认
shard key和foreignField对齐,否则性能断崖下跌
真正卡住聚合的,往往不是单条语句多慢,而是 maxTimeMS 缺失 + profiling 关闭 + $lookup 索引缺失这三者叠加。调参前先打开 system.profile,否则你优化的可能根本不是瓶颈点。











