复合索引字段顺序不能随意调换,因MongoDB B-tree索引按字典序存储,仅当查询含最左前缀时才生效;须遵循ESR原则(Equal/Sort/Range)设计顺序,否则索引可能完全失效。

复合索引字段顺序为什么不能随便调换
MongoDB 查询优化器是否能用上复合索引,高度依赖字段顺序。顺序错,db.collection.find() 可能直接退化为全表扫描,哪怕你建了三字段索引,查的时候只用了后两个字段,也可能完全失效。
根本原因是:MongoDB 的 B-tree 索引是按字典序存储的。比如索引 {a: 1, b: 1, c: 1},实际存的是 (a1,b1,c1)、(a1,b1,c2)、(a1,b2,c1)……这种嵌套排序结构。一旦跳过前缀(比如只查 b 和 c),就无法定位起始位置。
- 只有查询条件包含索引最左前缀时,索引才可能被使用;
{a: 1}、{a: 1, b: 1}、{a: 1, b: 1, c: 1}都可以走索引 -
{b: 1}或{b: 1, c: 1}不会走这个索引(除非有其他匹配的索引) - 范围查询(
$gt、$lt)之后的字段,无法用于索引过滤,只能用于排序或跳过——这是 ESR 原则里 “Range” 的关键限制
ESR 原则到底怎么落地到字段排序
ESR 是 Equal / Sort / Range 的缩写,不是理论,是 MongoDB 官方推荐的复合索引字段排序口诀。它本质是告诉你要把字段按“能精确匹配的放最左,要排序的紧跟着,最后才是范围条件”。
举个典型例子:查某城市下最近 10 条「已完成」订单,按创建时间倒序:
db.orders.find({
city: "shanghai",
status: "done",
created_at: {$gte: ISODate("2024-01-01")}
}).sort({created_at: -1}).limit(10)
对应最优索引是:{city: 1, status: 1, created_at: 1} —— 注意不是 {created_at: 1, city: 1, status: 1}。
-
city和status是 Equal 条件,放最左(且高频值区分度高的放更左,比如city比status更离散,优先) -
created_at是 Range($gte),必须放 Equal 字段之后;同时它又参与 Sort(sort({created_at: -1})),所以方向必须和 sort 一致(这里用1,因为索引升序 + sort 降序仍可高效扫描,MongoDB 支持反向遍历索引) - 如果把
created_at放第一位,city和status就变成非前缀条件,整个索引对这个查询无效
什么时候需要把 sort 字段单独提出来建索引
当查询中存在不匹配 ESR 顺序的排序需求时,强行塞进复合索引反而有害。比如同样查上海订单,但要按用户 ID 排序:.sort({user_id: 1}),而 user_id 并不在查询条件里。
这时建 {city: 1, status: 1, user_id: 1} 没用——因为 user_id 不是查询条件,索引无法定位数据块,最终还是得内存排序(sort stage 出现,慢且耗内存)。
- 真正有效的做法是:先用
{city: 1, status: 1}快速筛选出文档,再靠内存或游标分页处理排序 - 如果排序字段高频且数据量大,考虑加一个独立的
{user_id: 1}索引,但需权衡写入开销 - MongoDB 5.0+ 支持
$natural扫描优化,但不改变索引设计逻辑;别指望靠 hint 强制走错序索引来“绕过”ESR
建完索引一定要验证是否生效
很多人建完 createIndex() 就以为万事大吉,结果慢查依旧。MongoDB 是否真用上索引,只看 explain("executionStats") 里的 executionStages 节点。
关键看三个字段:stage 应该是 IXSCAN(不是 COLLSCAN),nReturned 应接近 totalDocsExamined(说明没做多余扫描),indexName 要匹配你建的那个。
- 用错索引常见于:查询用了
$or、正则没锚定开头(/^abc/可走索引,/abc/不行)、字段类型不一致(字符串查数字) - 聚合管道中,
$match阶段的位置很重要:必须放在$sort或$skip之前,否则索引可能被跳过 - 副本集环境下,secondary 节点默认不走索引做读操作(除非显式设置
readPreference: "primary"),测试务必在 primary 上做
索引不是建了就灵,字段顺序、查询写法、数据分布,三者稍有偏差,效果就断崖下跌。尤其是范围条件夹在中间、或者 sort 字段和查询条件完全不重叠的情况,最容易被忽略。










