复合索引字段顺序必须严格遵循 ESR 规则
在多条件查询场景中,并非简单地将所有 filter 字段塞入一个索引就能提升性能。MongoDB 高效利用复合索引的前提是“前缀连续段”,字段顺序直接决定索引能否命中以及扫描的数据量。ESR(Equality → Sort → Range)是一条硬性逻辑:等值查询字段必须放在最前面,排序字段居中,范围查询($gt、$in、$regex 等)必须放在最后。
例如以下慢查询:
db.items.find({
alxxxId: "ABC123",
state: 0,
itemTagList: { $in: ["tag1", "tag2"] },
persxxal: 0
})
该查询使用了 {alxxxId: 1, itemTagList: 1} 索引,但由于 itemTagList 属于 $in(范围操作),而 state 和 persxxal 这两个等值条件被直接跳过——索引根本未能利用它们的过滤能力,导致 keysExamined 和 docsExamined 高达 1498,最终仅返回 3 条文档。
- 正确顺序应为:
{alxxxId: 1, state: 1, persxxal: 1, itemTagList: 1}(三个等值字段在前,一个范围字段在后) - 如果还需要
.sort({createdAt: -1}),则必须将createdAt插在等值字段之后、范围字段之前:{alxxxId: 1, state: 1, persxxal: 1, createdAt: -1, itemTagList: 1} - 一旦范围字段出现在中间(例如
{alxxxId: 1, itemTagList: 1, state: 1}),其后所有字段的索引能力都会失效
避免用 $in 破坏索引前缀连续性
$in 在 MongoDB 中被归类为范围操作,而非等值查询。即便你写的是 {status: {$in: ["active"]}},它依然无法享受等值字段的索引穿透能力。这是一个容易被忽略的隐性陷阱。
常见错误场景:
- 误以为
{type: "user", tags: {$in: ["vip"]}}能通过{type: 1, tags: 1}索引高效过滤——实际上只能用到type前缀,tags部分会退化为内存过滤 - 用
$in查询数组字段时,若数组元素较多,索引条目会指数级膨胀,进而拖慢写入性能并增加内存占用 - 替代方案:如果业务上
$in的值集固定且较小(如状态枚举),可以考虑拆分成多个$or+ 单值查询,或使用部分索引(partialFilterExpression)进行预筛选
用 explain("executionStats") 验证是否真正走索引
不要因为“planSummary 显示 IXSCAN”就放松警惕。关键要看 executionStats 中的三个核心数字:
keysExamined:索引中实际进行比对操作的键数量docsExamined:从磁盘或内存加载并检查的文档数(理想情况下应 ≈nReturned)nReturned:最终返回的文档数
如果 keysExamined 为 2000,nReturned 仅为 5,说明索引筛选效率极低——大概率是字段顺序配置错误,或是使用了不支持索引的运算符(如 $ne、$not,或未加锚点的 $regex)。
执行示例:
db.items.find({
alxxxId: "ABC123",
state: 0,
persxxal: 0,
itemTagList: { $in: ["tag1"] }
}).explain("executionStats")
覆盖查询能完全绕过文档读取
当 find() 的查询条件加上投影字段({field: 1})全部被同一个索引覆盖时,MongoDB 无需加载原始文档,可直接从索引返回结果。这对于高并发只读场景(如列表页)能带来显著的性能提升。
要实现覆盖查询,必须满足以下条件:
- 索引包含所有
filter字段(按 ESR 规则排序) - 索引还包含投影中的所有字段(包括
_id,除非显式排除_id: 0) - 不能存在
$text、$where、聚合阶段等破坏覆盖能力的操作
例如:
db.items.find(
{alxxxId: "ABC123", state: 0},
{alxxxId: 1, state: 1, title: 1, _id: 0}
).explain("executionStats")
对应的索引应为:{alxxxId: 1, state: 1, title: 1}。此时 executionStats 中的 docsExamined 会显示为 0。
一个容易被忽视的细节是:即使索引字段齐全,只要投影里多了一个未建在索引中的字段(例如遗漏了 title),整个覆盖查询就会立即失效,docsExamined 会立刻回升。
