$match必须位于pipeline最前端才能触发索引下推,否则因优化器无法关联索引而失效;且需满足等值最左前缀匹配、排序字段顺序与方向严格一致等条件。

聚合查询无法命中部分复合前缀索引,根本原因不是索引没建,而是 $match 条件不满足最左前缀匹配,或后续 stage 破坏了索引下推条件。
为什么 $match 不写在 pipeline 开头就必然失效
MongoDB 聚合优化器只在 $match 位于 pipeline 最前端时,才尝试将其下推到存储层并关联索引。一旦前面有 $addFields、$project 或 $lookup,整个过滤逻辑就脱离索引控制范围。
-
$match必须是第一个 stage;中间插任何非$match操作,索引前缀匹配即中断 - 即使字段完全匹配索引定义(如索引为
{userId: 1, status: 1}),但 pipeline 是[{$project: {userId: 1}}, {$match: {userId: "U123"}}],索引也不会被使用 - 验证方式:用
db.coll.explain("executionStats").aggregate([...])查看executionStages.stage是否为IXSCAN;若为COLLSCAN或TEXT,说明前缀未命中
等值字段缺失导致前缀断裂
复合索引 {a: 1, b: 1, c: 1} 要生效,查询至少得提供 a 的等值条件。跳过 a 直接查 {b: 2, c: 3},索引完全不可用——这不是“部分命中”,而是彻底退化。
- 常见错误:业务逻辑中把
status当主筛选字段,却建了{createdAt: 1, status: 1}索引,结果{status: "done"}查询全表扫 - 范围条件(
$gt、$lt、$in)后所有字段自动失效:如索引{a: 1, b: 1, c: 1},查询{a: 1, b: {$gt: 2}, c: 3}中的c不走索引 - 解决思路:把必须等值的字段前置,范围字段后置;必要时拆分索引,例如为
c单独建{c: 1}
排序字段顺序与索引不一致触发内存 SORT
哪怕 $match 完全命中索引前缀,如果 $sort 字段顺序或方向和索引不严格一致,MongoDB 仍会额外执行内存排序,且该阶段不计入索引扫描范围。
- 索引为
{userId: 1, status: 1, createdAt: -1},查询带.sort({status: 1, userId: 1})就无法利用索引排序——因为索引中是先按userId排,再按status排,和需求顺序冲突 -
$sort必须紧跟$match后,中间不能有$lookup或$unwind;否则优化器放弃下推 - 方向必须全匹配或全反向:索引
{a: 1, b: -1}可支持.sort({a: 1, b: -1})或.sort({a: -1, b: 1}),但不支持.sort({a: 1, b: 1})
最危险的情况是:索引存在、查询看起来也“对得上”,但 explain() 显示 IXSCAN 后跟 SORT 或 COLLSCAN —— 这说明你正在用索引做无效过滤,而真正耗资源的部分(排序/范围扫描)仍在内存里硬扛。











