$facet不能直接加速分页,因其不支持索引且强制全表扫描;必须前置高选择性$match,子管道内需用$limit、$project等控制内存与体积。

为什么$facet不能直接加速分页
$facet不是并发执行器,它只是把同一份输入文档流分发给多个子管道处理。你加了$facet后分页变慢,大概率是因为它被放在了$match前面,导致全表扫描;或者子管道里用了$unwind展开大数组、$group没加$limit,触发了100 MB内存硬限——报错是ExceededMemoryLimit,不是BSON大小超限,allowDiskUse对它完全无效。
必须前置的外层$match才能走索引
只要$facet是管道第一个阶段,MongoDB就强制COLLSCAN,索引失效。正确顺序只能是:
-
$match(高选择性条件,如{status: "active", createdAt: {$gte: ISODate("2026-01-01")}}) -
$facet(再分发给各子管道)
子管道内部的$match不走索引,只接收上游已过滤后的文档流。所以别在$facet里写{$match: {type: "book"}}——它已经没用了。
子管道怎么控制内存不爆
每个子管道输出结果受100 MB硬限,常见炸点:
-
$unwind一个含5000个标签的数组字段 → 立刻膨胀成5000份文档 -
$group后直接$sortByCount,没$limit→ 中间结果可能撑爆内存 - 子管道返回大量原始文档(比如只
$project但没删字段)→ 文档体积失控
实操建议:
- 外层
$match尽可能收紧数据范围 - 子管道内加
{$limit: 100}或{$slice: 10}控制数组输出 - 用
$project显式排除大字段(如content: 0),尤其在$facet前已投影过一次时
多面分页的典型结构怎么搭
比如你要同时返回「当前页商品列表」+「按类目统计数」+「价格区间分布」,结构应类似:
{ $facet: {
"items": [ {$skip: 20}, {$limit: 20}, {$project: {name: 1, price: 1, _id: 0}} ],
"categories": [ {$group: {_id: "$category", count: {$sum: 1}}}, {$limit: 10} ],
"priceRanges": [ {$bucketAuto: {groupBy: "$price", buckets: 5}} ]
} }
注意三点:
-
"items"子管道必须带$skip/$limit,且跳过量要由应用层计算好传入 -
$bucketAuto也受100 MB限制,数值字段才可用,非数值字段得先$convert或换$bucket - 最终结果是一个单文档,含三个字段,每个字段值都是数组——别忘了在应用层解包
result.items、result.categories
最易忽略的是:子管道之间完全隔离,无法共享中间状态;所有剪枝和精简必须在进入$facet前做完,进了就晚了。











