查聚合操作是否真在吃内存:用 db.currentop() 过滤长时、高内存消耗操作,执行 db.currentop({ "secs_running": { "$gt": 10 }, "command.aggregate": { "$exists": true }}),重点关注 ns、secs_running、numyields、locks 和 query 字段;结合 mongostat 观察 faults/s > 50、dirty > 70%、used > 90%、locked % 突增等内存压力信号;kill 前确认 client 和执行时长,避免误杀关键任务;事后用 {explain: true} 复现,重点检查 useddisk、totaldocsexamined、sort memusage 等指标定位根因。

查聚合操作是否真在吃内存:用 db.currentOp() 过滤长时、高内存消耗操作
聚合操作本身不直接暴露内存占用值,但长时间运行、触发磁盘落盘(allowDiskUse:true)或扫描大量文档的操作,会显著拉升 res(常驻内存)和 faults/s(页错误)。先确认它是不是“真凶”:
进 mongo shell 后执行:
db.currentOp({
"secs_running": { "$gt": 10 },
"command.aggregate": { "$exists": true }
})
重点关注:
• ns:集合名是否是大表(如 logs、events)
• secs_running:持续超 10 秒基本已脱离正常响应节奏
• numYields 和 locks:若频繁 yield 或持有集合级锁,说明内存不足被迫让出 CPU 做换页
• query 字段里是否有未加 $limit、无索引 $match 或多层 $lookup
识别内存压力信号:mongostat 中这几个字段比 QPS 更关键
mongostat 不显示单个操作内存,但能反映系统级内存紧张迹象。在主节点上运行 mongostat -n 30,盯住以下三列:
-
faults/s> 50:表示内核频繁从 swap 或磁盘换入页面,WiredTiger 缓存严重不足,聚合很可能被挤出内存 -
dirty> 70% 且used> 90%:WiredTiger 缓存几乎全被脏数据占满,新聚合进来只能等刷盘或直接 OOM -
locked %突增 +q r|w队列变长:不是锁本身问题,而是内存不足导致操作排队、上下文切换激增,CPU 反而可能不高
注意:mongostat 的 res 列(物理内存使用 MB)是进程级总览,不能定位到某个聚合,但它长期 >80% 服务器总内存时,currentOp 里查到的聚合就极可能是主因。
快速止损:kill 操作前先确认它没在写关键数据
别一看到长时聚合就 db.killOp()。有些聚合是后台归档、报表生成或 ETL 任务,杀错会导致数据不一致。操作前检查:
- 看
client字段:如果是localhost:27017或已知调度服务 IP,可联系对应团队确认是否可中断 - 看
secs_running和microsecs_running:刚启动 2 秒就卡住,大概率是索引缺失;跑 3 分钟只推进了 0.1%,很可能是$unwind膨胀爆炸或$lookup关联了千万级集合 - 避免直接 kill:先用
db.killOp(<opid>)</opid>,不要用db.killAllSessions(),后者会干掉所有连接,包括监控和副本集心跳
后续验证:用 $explain 复现并锁定内存瓶颈点
kill 掉后别停——立刻拿原聚合语句加 {explain:true} 重跑,重点看 executionStats:
db.orders.aggregate([...], {explain:true})
关键字段含义:
-
executionTimeMillis> 500:已超阈值,需优化 -
totalDocsExamined远大于nReturned:说明$match没走索引,全表扫了 -
usedDisk: true:明确提示内存不够,强制落盘 —— 这是内存过高的直接证据 -
stage是SORT且memUsage> 32MB:排序阶段爆内存,必须加索引或改用$merge替代
真正难处理的不是“哪个聚合在吃内存”,而是“为什么它非得吃这么多”。$explain 输出里没出现 memUsage 字段?说明你用的是 MongoDB 4.2 以下版本,得靠 serverStatus().metrics.queryExecutor 间接估算 —— 这个细节,多数人会忽略。











