复合索引字段顺序错误导致索引失效:等值查询字段必须在前,范围查询和排序字段紧随其后;顺序颠倒会使mongodb全量扫描再内存过滤,造成totaldocsexamined远大于nreturned。

totalDocsExamined 远大于 nReturned,说明索引没真正“过滤到位”,不是数据多,是索引建得不对。
为什么复合索引字段顺序一错就全白搭
MongoDB 的复合索引按字段顺序严格匹配:等值查询字段必须在前,范围查询($gte、$lt)和排序字段紧随其后。一旦把范围字段放在等值字段前面,索引就只能扫出一大片,后续再靠内存 filter——这就是 totalDocsExamined 暴涨的根源。
比如你建了 { deliverytime: 1, producttags: 1 },但查询是 { producttags: { $exists: true }, deliverytime: { $gte: 1656864000000 } },MongoDB 只能先按 deliverytime 扫几百万条,再逐个检查 producttags 是否存在——totalDocsExamined 高达 49800000 就是这么来的。
- 等值条件(如
status: "paid")必须放最左 - 多个等值条件,把区分度高的放前面(例如
currency: "USD"比status: "paid"更稀疏,优先) - 范围条件(
$gte、$in、$regex)只能放等值之后,且最多一个 - 排序字段(
sort({ paidAt: -1 }))可接在范围字段后,但不能跨过范围字段插队
怎么验证当前索引是否真在干活
别只看 db.collection.getIndexes(),要跑真实查询 + explain("executionStats"):
- 检查
executionStages.stage是IXSCAN还是COLLSCAN—— 后者说明根本没走索引 - 盯住
totalDocsExamined和nReturned的比值;理想情况应 ≤ 2×nReturned - 如果
keysExamined远大于nReturned,说明索引条目太多被跳过,大概率是多键索引膨胀或字段顺序反了 - 用
hint()强制走某个索引,对比耗时变化,排除优化器误选
实际改索引的三步操作
假设原查询是:db.payments.find({ currency: "USD", status: "paid", amount: { $gte: 100 } }).sort({ paidAt: -1 }),而现有索引是 { amount: 1, currency: 1, status: 1 }:
- 删掉低效索引:
db.payments.dropIndex({ amount: 1, currency: 1, status: 1 }) - 重建复合索引:
db.payments.createIndex({ currency: 1, status: 1, amount: 1, paidAt: -1 }) - 立刻验证:
db.payments.find({ currency: "USD", status: "paid", amount: { $gte: 100 } }).sort({ paidAt: -1 }).explain("executionStats"),确认totalDocsExamined接近nReturned
注意:数组字段(如 tags: ["a", "b"])一旦进入索引,会触发 multikey: true,导致右侧字段的范围/排序失效——这种字段尽量别放复合索引中间或左侧。











