只有首个$match能走索引,因MongoDB优化器仅将其映射为底层索引扫描;后续所有阶段均作用于内存中间结果,与索引无关;若首阶段非$match或含$expr、无锚点$regex等,则整条管道退化为全表扫描。

聚合管道里只有最开头的 $match 阶段可能走索引,其余阶段一律不走——这不是配置问题,是 MongoDB 的执行模型决定的。
为什么只有第一个 $match 能走索引
MongoDB 优化器只把聚合管道的第一个 $match 映射为底层集合的索引扫描操作;后续所有阶段(包括第二个 $match、$sort、$project、$lookup)都作用于内存中的中间结果,和原始索引完全无关。
- 如果第一个阶段不是
$match(比如是$lookup或$unwind),整个管道彻底失去索引能力 -
$match必须写在管道最前端,且不能被$expr、无锚点$regex、或字段不存在等条件破坏下推能力 - 即使你建了
{status: 1, createdAt: -1}索引,但写成{$match: {createdAt: {$gt: ...}}}单独出现,没带status等值条件,索引前缀不匹配,仍退化为全表扫描
$sort 在聚合里根本不会用索引排序
$sort 阶段从不直接使用 B-tree 索引做排序运算,但它可以“借”索引的物理顺序规避内存排序——前提是满足三个硬性条件:复合索引存在、$match 是前缀等值查询、$sort 紧跟其后且字段顺序一致。
- 常见错误写法:
[{$match: {status: "done"}}, {$addFields: {...}}, {$sort: {createdAt: -1}}]→ 中间插了$addFields,$sort必然内存排序 - 即使满足顺序,若索引是
{status: 1, createdAt: -1},但$match写成{status: {$in: [...]}},部分版本可能拒绝下推 -
.hint()对聚合无效——调用会报错或静默忽略,别试
怎么确认索引到底有没有被用上
别信返回结果对不对,要看 explain("executionStats") 里真实执行路径:
- 重点看
executionStages.stage:要是"IXSCAN"且出现在最外层inputStage,说明第一个$match真走了索引 - 检查
totalKeysExamined是否接近nReturned;如果totalDocsExamined远大于nReturned,说明索引过滤效率差或根本没用上 - 如果看到
"stage": "SORT"且memUsage很大,或报Sort exceeded memory limit,证明$sort没借到索引顺序,正在纯内存排序 -
$indexStats里的accesses.ops为 0 不代表没走索引——它只统计真正打到底层集合的访问,聚合中中间阶段的$match不计入
最容易被忽略的是:索引是否生效,取决于查询写法与索引结构的精确咬合,而不是“看起来应该能行”。哪怕只多一个 $project、少一个等值字段、正则缺个 ^,整条管道就退回 COLLSCAN。

















