MongoDB复合索引严格遵循最左前缀原则:查询必须从索引最左字段开始连续匹配,跳过任一字段则右侧全部失效;范围查询、排序方向不一致或低选择性字段前置均导致索引效率下降甚至全表扫描。

查询条件没从最左字段开始连续匹配
MongoDB 复合索引严格遵循最左前缀原则,不是“包含字段就用”,而是必须从定义的第一个字段开始,连续、无跳过地匹配。一旦中断,右侧所有字段都不参与索引查找。
-
{userId: 1, status: 1, createdAt: -1}索引下,{status: "PENDING"}完全不走索引,执行计划里是COLLSCAN -
{userId: "U123", createdAt: {"$gt": ISODate("2026-01-01")}}只用到userId,createdAt条件被丢弃——因为跳过了中间的status - 即使字段顺序调换写成
{status: "PENDING", userId: "U123"},只要索引是{userId: 1, status: 1},它仍能命中(MongoDB 会自动重排等值条件),但{status: "PENDING"}单独出现就不行
范围查询($gt、$in、$regex)出现在排序或等值字段之前
范围操作会截断索引有效前缀。B-tree 结构决定了:一旦遇到范围条件,它右边的所有字段既不能用于过滤,也不能用于排序。
- 索引
{service: 1, level: 1, timestamp: -1}配合.find({service: "api", level: {$in: ["error", "warn"]}}).sort({timestamp: -1}),timestamp无法用于排序,执行计划中会出现"stage": "SORT"节点 - 正确做法是把排序字段提前、范围字段放最后:
{service: 1, timestamp: -1, level: 1},这样level的$in不影响timestamp排序 -
$ne、$not、$regex(非前缀,如/son$/)也都算范围语义,同样导致后续字段失效
排序字段方向或顺序与索引不一致
索引中字段的排列顺序和方向,必须和 .sort() 完全一致,或全部反向;否则排序阶段无法下推,只能 FETCH 后内存排序。
- 索引
{a: 1, b: -1}支持.sort({a: 1, b: -1})和.sort({a: -1, b: 1}),但不支持.sort({b: 1, a: 1})——哪怕字段相同,顺序反了就不认 - 执行计划里如果看到
IXSCAN+SORT并存,说明索引只用了过滤,排序没覆盖 - 字符串字段若建索引时加了
collation(如{collation: {locale: "zh"}})),查询也必须显式带相同collation,否则不命中
低选择性字段放在最左,导致索引剪枝能力极差
把取值少的字段(如 status 只有 3 个值)放在复合索引最左,相当于强制 MongoDB 先分几大桶,每桶再遍历大量文档,IO 和内存开销反而上升。
-
explain("executionStats")中若nReturned远小于nScannedObjects,就是剪枝失败的信号 - 索引体积变大,缓存命中率下降,写入性能也受影响(每个写操作都要更新更多索引页)
- 更糟的是:即使你加了高选择性字段(如
userId)在后面,只要它不在最左,整个索引对不含最左字段的查询就完全无效
实际验证时,别只看有没有索引名出现在 explain 输出里,重点盯 executionStages.stage 是不是 IXSCAN、totalDocsExamined 是否接近 0、有没有额外的 SORT 或 FETCH 节点——这些才是索引是否真正生效的铁证。











