复合索引字段顺序决定是否命中索引:必须从最左字段连续匹配,否则索引失效;排序字段顺序与方向须与索引完全一致或全反向;低选择性字段前置会降低索引效率。

复合索引字段顺序直接影响能否命中索引
MongoDB 的复合索引是按字段定义顺序构建 B+ 树的,查询时必须从最左字段开始连续匹配,否则索引直接失效。这不是“性能差一点”,而是 IXSCAN 阶段根本不会出现——执行计划里会看到 COLLSCAN 或 IXSCAN 后跟 SORT,说明排序和过滤都没走索引。
- 假设索引为
{userId: 1, status: 1, createdAt: -1},以下查询能命中:{userId: "U123"}、{userId: "U123", status: "PENDING"}、{userId: "U123", status: "PENDING", createdAt: {"$gt": ...}} - 但
{status: "PENDING"}或{createdAt: {"$gt": ...}}单独查,完全不走该索引 - 哪怕加了
{userId: "U123", createdAt: {"$gt": ...}},跳过中间的status字段,也只用到userId前缀,createdAt条件无法下推到索引层
排序字段顺序必须和索引字段顺序一致才能避免内存排序
如果查询带 .sort({status: 1, userId: 1}),而索引是 {userId: 1, status: 1},MongoDB 无法利用该索引完成排序——因为索引中数据是先按 userId 排、再按 status 排,和查询要求的顺序冲突。结果就是:索引可用于过滤(如果条件含 userId),但排序仍需额外 SORT 阶段,消耗内存且易触发 Execution Time Limit Exceeded。
- 支持排序的唯一前提是:查询中的
sort()字段顺序 + 方向,与索引定义完全一致,或全为反向(如索引{a: 1, b: -1}可支持.sort({a: -1, b: 1})) -
.sort({b: 1, a: 1})永远无法用{a: 1, b: 1}索引加速,即使字段相同 - 使用
explain("executionStats")查看输出中executionStages.stage是否为IXSCAN且无SORT节点,是最直接的验证方式
低选择性字段放前面会让整个索引失效
字段选择性(distinct value / total records)决定索引的剪枝能力。把 status(比如只有 "PENDING"/"DONE"/"FAILED")放在 userId 前面,相当于先分三大桶,每桶里再找用户——每桶数据量巨大,B+ 树深度没减少,IO 和内存开销反而上升。
- 高频等值查询字段(如
tenantId、orgId)适合前置,但前提是它本身有足够区分度(租户数 > 1000) - 范围查询字段(
{"$gt": ...})应放在等值字段之后,且只能有一个——后续字段全部失效 - 排序字段若为范围条件,基本无法参与索引优化;降序字段(
-1)在多字段排序中仅当所有字段同向或全反向时才有效
Spring Data MongoDB 中 @CompoundIndex 容易忽略的细节
@CompoundIndex 注解生成的索引是静态的,但业务查询模式会变。很多人写完注解就以为万事大吉,其实每次新增查询路径都得重新评估索引覆盖性。
-
@CompoundIndex(def = "{'status': 1, 'userId': 1}")在 Spring Boot 启动时创建,但若实际查询只用userId,这个索引几乎没用——不如单独建{userId: 1} - 多个 @CompoundIndex 可能互相干扰,尤其当字段重叠时,MongoDB 会选一个 winningPlan,但未必是你期望的那个
- 用
mongoTemplate.getCollection("orders").listIndexes()手动确认索引是否存在、字段顺序是否符合预期,比依赖日志更可靠
explain() 输出里的 indexBounds 和 direction。


















