Range字段必须置于复合索引末尾,因B-tree索引按字段顺序嵌套排序,范围查询会破坏后续字段的全局有序性,导致排序无法下推、过滤退化为内存扫描;explain中totalKeysExamined远大于nReturned即为典型信号。

Range 字段必须放在复合索引的最后,否则排序无法下推、范围过滤会退化为内存扫描——这不是“建议”,而是 B-tree 索引结构决定的硬约束。
为什么 Range 字段不能在 Sort 字段后面?
MongoDB 的复合索引是按字段顺序嵌套排序的:先排第一个字段,相同值内再排第二个,依此类推。一旦遇到 {$gt}、{$in}、{$regex} 这类范围条件,后续字段就失去全局有序性。
- 比如索引
{status: 1, created_at: -1, amount: 1},查询.find({status: "paid", amount: {$gt: 100}}).sort({created_at: -1})只能利用前两个字段;amount是范围,它后面的created_at已无法用于排序 -
explain("executionStats")中若出现"nReturned" 或 <code>"executionStages.stage" === "SORT",基本就是 Range 位置错位的铁证 - 即使
amount字段选择性很高,放中间或前面也无济于事——B-tree 不支持“跳过中间层继续排序”
ESR 不是经验法则,是索引生效的前提条件
ESR(Equality → Sort → Range)不是“推荐顺序”,而是查询能否走索引下推的分水岭。违反它,MongoDB 就只能用上部分字段,甚至 fallback 到 collection scan。
-
Equality字段(如service: "api")必须最左,保证快速缩小扫描范围 -
Sort字段(如timestamp: -1)紧随其后,且方向必须与.sort()完全一致;{a: 1, b: -1}无法支撑.sort({a: 1, b: 1}) -
Range字段(如level: {$in: ["error", "warn"]})必须放最后,让它只在已限定的 Equality+Sort 结果集内做局部扫描 - 错误示例:
{service: 1, level: 1, timestamp: -1}——level是$in,它后面的timestamp排序失效,必然触发SortStage
实际建索引时容易忽略的细节
字段顺序定生死,但几个实操细节常被跳过,直接导致索引“建了等于没建”:
- 复合索引中所有
Sort字段的方向(1或-1)必须和查询中.sort()的顺序、方向完全一致;混用升序降序会直接 miss 索引 - 如果查询同时含
.sort({a: 1, b: -1}),索引必须是{a: 1, b: -1},写成{a: 1, b: 1}就不命中 -
totalDocsExamined > totalKeysExamined是最直观的信号——说明索引返回了大量键,但还要回表过滤,大概率是 Range 放太前或 Sort 方向不匹配 - 不要为了“看起来更全”把字段堆进索引;超过 5 个字段的复合索引维护成本高,且前缀匹配效率下降明显
真正卡住性能的,往往不是没建索引,而是 Range 字段悄悄混进了 Sort 后面——查 executionStats 里的 stage 链和文档扫描量,比调优语句本身更关键。

















