$facet是单次输入、多路分发的确定性机制,避免重复扫描集合以减少I/O和网络开销;其子管道中间结果受100MB内存硬限,allowDiskUse无效,需靠外层$match或子管道内$limit前置剪枝。

$facet 不是“并行执行”的魔法开关,而是单次输入、多路分发的确定性机制。 它不会提升 CPU 或 I/O 并行度,但能避免重复扫描集合,显著减少网络往返和磁盘读取次数——这才是它在真实业务中被高频使用的根本原因。
为什么 $facet 查询会突然报 ExceededMemoryLimit?
这是最常踩的坑:$facet 内每个子管道的中间结果(不是最终输出)受 100 MB 内存硬限制,且 allowDiskUse 对它完全无效。
- 错误典型表现:
ExceededMemoryLimit,而不是超 BSON 16MB 的BSONObjTooLarge - 触发点往往在子管道用了
$unwind+ 大数组,或$group后未及时$limit - 例如:对一个含 5000 个标签的
tags字段做$unwind,再$sortByCount,极易撑爆内存 - 解法不是加内存,而是前置剪枝:
$match放在 $facet 外层,或子管道内用$limit控制输出条数
$facet 必须配合 $match 才能走索引?
不是“必须”,但顺序决定性能。$facet 本身不触发索引,但它前面的阶段可以。
- 如果
$facet是管道第一个阶段 → 强制COLLSCAN,全表扫描不可避免 - 如果
$match或$sort在$facet前 → 这些阶段可走索引,$facet 只处理已过滤/排序后的文档集 - 注意:
$facet内部子管道不能再用索引加速,它们只接收上游传来的文档流 - 实操建议:把高选择性过滤(如
{status: "active", createdAt: {$gt: ...}})严格放在 $facet 外层
如何安全地组合 $bucket 和 $sortByCount 在不同 facet 中?
可以,而且这是典型用法,但要注意字段路径一致性与空值处理。
-
$bucket要求字段值为数字;$sortByCount对字符串或嵌套字段都有效,但需明确路径(如"$tags"或"$category.name") - 若某文档缺失
price字段,$bucket会跳过该文档;而$sortByCount对null或缺失字段默认计为一个分组,可能产生意外的"null"条目 - 规避方法:子管道内加
{$match: {price: {$exists: true, $type: "number"}}}或{$match: {tags: {$ne: null}}} - 别指望一个子管道输出直接喂给另一个——
$facet的各字段彼此隔离,后续聚合要用$project+$arrayElemAt显式提取
真正难的从来不是写对语法,而是预估子管道内存开销。线上环境里,一个没加 $limit 的 $sortByCount 子管道,在数据量翻倍后第二天就可能让整个报表聚合挂掉——这种故障不会报错在开发机上,只会在凌晨三点的生产日志里安静爆发。


















