聚合操作卡住连接不释放,本质是查询未设超时且未走索引的重型聚合(如含$lookup、多层$unwind、无limit的sort),执行超500ms即导致连接长期处于query状态。

聚合操作卡住连接不释放,本质是查询没设超时 + 没走索引
重型聚合(比如带 $lookup、多层 $unwind、无 limit 的 sort)一旦执行时间超过 500ms,就容易让连接长期停留在 QUERY 状态。这时 db.serverStatus().connections.current 和 active 会高度接近,说明连接真正在“干活”,不是空闲堆积——但干的是低效活。
- 先确认是否真有慢聚合:开启 profiling:
db.setProfilingLevel(1, { slowms: 500 }) - 查最耗时的聚合:
db.system.profile.find({ millis: { $gt: 500 }, command.aggregate: { $exists: true } }).sort({ millis: -1 }).limit(3) - 对命中记录用
explain("executionStats")看executionTimeMillis和nReturned是否严重失衡(比如耗时 3s 只返回 2 条) - 重点检查缺失索引的字段:聚合 pipeline 中第一个
$match阶段的字段必须有索引,否则全表扫描直接拖垮连接
Java 驱动里 aggregate() 默认不设超时,得手动加
Java 驱动的 MongoCollection.aggregate() 方法默认没有 socket timeout 或 maxTimeMS,一个卡死的聚合会一直占着连接,直到客户端线程超时或连接池回收(而老版本驱动压根不回收空闲连接)。
- 必须显式设置
maxTimeMS:collection.aggregate(pipeline).maxTime(30, TimeUnit.SECONDS) - 配合连接池参数:确保
connectionsPerHost ≤ 50、maxConnectionIdleTimeMS=60000,否则即使加了maxTimeMS,连接也回不去池子 - 别依赖
threadsAllowedToBlockForConnectionMultiplier来“扛”慢查询——它只是允许线程排队,不解决连接被占问题 - 驱动版本必须 ≥ 4.3,低于 4.0 的
maxTimeMS在某些 pipeline 场景下可能不生效
副本集里聚合路由到 secondary 会放大连接压力
如果应用用了 readPreference=secondaryPreferred 并在 secondary 上跑重型聚合,不仅 primary 少了个分担者,secondary 还要额外处理 oplog 同步 + 用户查询,连接数更容易打满。
- 重型聚合一律强制走
primary:collection.aggregate(pipeline).readPreference(ReadPreference.primary()) - secondary 仅用于轻量
find或报表类只读查询,且必须配maxTimeMS和limit - 检查是否有 driver 自动 fallback 到 secondary 的逻辑(比如
secondaryPreferred+ primary 偶尔抖动),这种隐式切换会让聚合突然出现在不该出现的节点上 - 监控每个节点的
db.currentOp({ "secs_running": { "$gt": 5 } }),比只看总连接数更能定位哪台在扛聚合
聚合结果集太大导致连接卡在 fetch 阶段
聚合输出没加 $limit,或 $group 后没 $project 精简字段,会导致大量数据从 mongod 往 driver 传输,socket 缓冲区占满、连接 hang 在 GETMORE 状态,netstat 里能看到一堆 ESTABLISHED 连接不释放。
- 所有聚合 pipeline 结尾必须有
$limit(哪怕先设1000,业务层再分页) - 用
$project显式声明需要的字段,避免把整个文档(含大文本、二进制)全拉回来 - Node.js 驱动要注意:
cursor.toArray()会一次性加载全部结果到内存,高风险;改用cursor.forEach()流式处理 - Java 驱动中避免
AggregateIterable.into()直接灌大集合,优先用forEachRemaining()控制内存水位











