索引字段顺序错误会导致查询退化为全表扫描(COLLSCAN);必须遵循ESR原则:等值条件(Equal)放最左,排序字段(Sort)次之且方向一致,范围条件(Range)放最后;验证需通过explain检查IXSCAN、nReturned与totalKeysExamined是否匹配,避免误判索引生效。

写反了,索引大概率不生效——不是变慢,而是直接退化为全表扫描(COLLSCAN),尤其当跳过最左字段或把范围条件放在等值条件之前时。
为什么status放前面会导致userId查询失效
复合索引是 B+ 树结构,按定义顺序逐层排序。索引 {"status": 1, "userId": 1} 意味着数据先按 status 分桶,每桶内再按 userId 排序。此时:
-
{status: "PENDING"}能定位到一个或几个桶,走IXSCAN -
{userId: "U123"}必须遍历所有status桶找该用户,无法利用索引顺序 →COLLSCAN -
{status: "PENDING", userId: "U123"}可用,但效率不如{userId: "U123", status: "PENDING"},因为前者桶太大(status选择性低)
范围字段写在等值字段前面会引发内存排序
比如查询 db.orders.find({amount: {$gt: 100}, userId: "U123"}).sort({createdAt: -1}),若索引建为 {"amount": 1, "userId": 1, "createdAt": -1}:
-
amount是范围条件,B+ 树在此之后不再保证userId和createdAt的全局有序性 - 索引只能用于过滤
amount,userId变成内存过滤,createdAt排序完全无法下推 → 执行计划中必现SORT阶段 -
explain("executionStats")中"totalDocsExamined"远大于"totalKeysExamined"就是典型信号
如何按过滤效能重排字段:盯住 ESR 原则
ESR 不是建议,是 MongoDB 索引匹配的底层逻辑:Equal → Sort → Range。重排前先归类字段用途:
-
Equal 字段:高频等值查询,且高选择性(如
tenantId、userId),必须放最左 -
Sort 字段:出现在
.sort()中,方向(1或-1)必须与索引定义一致;多个字段排序时,顺序也必须严格一致 -
Range 字段:含
{$gt}、{$lt}、{$in}等,只应放在最后,且尽量只有一个
例如查询 {orgCode: 350119, fixedStatus: {$in: [1,2]}, _id: {$gt: ObjectId(...)}},正确索引应为 {"orgCode": 1, "fixedStatus": 1, "_id": 1},而非原事故中倒置的 {"orgCode": 1, "_id": -1}(漏掉 fixedStatus)或把 _id 放前(它实际是范围条件)。
验证是否真用了索引,别信“我建了”
Spring Boot 中用 mongoTemplate.findOne(query.explain(), clazz),Mongo Shell 中跑 db.collection.find(filter).explain("executionStats"),重点看:
-
"stage": "IXSCAN"→ 索引被用于扫描 -
"nReturned"≈"totalKeysExamined"→ 过滤高效,无大量无效键扫描 - 输出中没有
"stage": "SORT"→ 排序由索引完成,未触发内存排序 - 若出现
"stage": "COLLSCAN"或"totalDocsExamined" > "totalKeysExamined" * 2,说明字段顺序或覆盖不全
真正容易被忽略的是:索引创建后没验证执行计划,就默认它生效;或者只验证了单字段查询,没覆盖组合条件 + 排序 + 范围的完整链路。


















