$sort必须全量加载文档到内存是因为其非流式处理机制,需一次性读取所有匹配文档的排序字段及后续阶段所需字段才能完成排序;32MB(internalQueryExecMaxBlockingSortBytes)限制适用于find或简单聚合中的$sort,100MB限制则用于聚合管道中$sort或$group阶段,加索引可绕过内存排序直接按B-tree顺序读取,而allowDiskUse(true)仅是磁盘兜底方案,并非性能优化。

$sort为什么必须全量加载文档到内存
因为$sort不是流式处理——它得先把所有匹配文档的排序字段(以及后续阶段要用的字段)一次性读进内存,才能比较、交换、排好序。哪怕你只想要前10条,MongoDB也得先把全部结果拉进来再截断。
常见错误现象:
- 查100万条订单按
created_at排序,但没建索引 → 全表扫描 + 全字段加载 → 内存爆掉 - 聚合里
$sort写在$project后面,结果把content、images等大字段也拖进内存 - 用
$sort配合$skip/$limit做分页,却没前置$match过滤时间范围,导致扫描三年数据
32MB和100MB两个限制分别管什么
这两个数值不是随便定的,对应不同场景:
-
internalQueryExecMaxBlockingSortBytes(默认33554432字节 ≈ 32MB):控制单次find或简单聚合中$sort阶段的内存上限。没索引时必走这个路径 - 聚合管道中的
$sort或$group触发的是另一套限制:104857600字节(100MB),报错提示为Sort exceeded memory limit of 104857600 bytes
注意:MongoDB 6.0+ 默认开启allowDiskUseByDefault: true,会自动落盘;但5.x及更早版本必须显式传{allowDiskUse: true},否则超限直接失败。
为什么加索引就能绕过内存限制
加索引不是“省内存”,而是让MongoDB根本不用SORT阶段——它直接按B-tree顺序从磁盘读数据,边读边返回,不缓存全文档。
实操要点:
- 索引字段顺序必须匹配
$sort键顺序,比如{$sort: {status: 1, created_at: -1}},索引就得建为{status: 1, created_at: -1} - 复合索引里别混入低区分度字段(如
type: "order"),否则索引体积大、命中率低 - 用
db.collection.explain("executionStats").find(...).sort(...)确认执行计划里有没有SORTstage
allowDiskUse: true不是万能解药
它只是允许mongos或mongod把临时排序数据写到磁盘,但实际能否生效,取决于三个容易被忽略的点:
- mongos启动时没配
--tempPath,默认写/tmp,而很多容器里/tmp是tmpfs内存盘,只有256MB - 磁盘路径权限不对或空间不足,日志里会出现
No space left on device,但客户端只看到超时或空响应 - 在sharded cluster里,如果
$sort字段不是分片键,mongos得合并所有分片结果再排序,中间数据量可能远超单机预期
真正稳的做法,是把$match前置、$project裁剪、索引对齐、再辅以allowDiskUse: true兜底——少一个环节,都可能在某次数据量波动时突然崩掉。











