复合索引必须严格匹配查询模式、esr顺序(等值→排序→范围)及数组字段特殊规则,否则易触发collscan;电商多维筛选慢主因是索引未覆盖查询、顺序错误或数组字段未建路径索引。

复合索引不是“多字段堆一起就快”,在电商多维统计场景中,它必须严格匹配查询模式+ESR顺序+数组字段特殊规则,否则 explain() 里 winningPlan.stage 很可能还是 COLLSCAN。
为什么电商多维筛选查得慢?
典型电商筛选如“分类=手机 & 品牌∈[Apple, Xiaomi] & 价格∈[3000,8000] & 评分≥4.5 & 上架时间≤30天”,一旦涉及数组字段(如 attributes)、范围条件、排序,MongoDB 容易退化为全文档扫描。
常见错误现象:
-
totalDocsExamined远大于nReturned(比如返回20条却扫了50万文档) - 加了单字段索引(
{category: 1}、{price: 1})没用 - 用了
$elemMatch查attributes却没建对应路径索引
复合索引字段顺序必须遵守ESR原则
ESR不是建议,是B-tree索引生效的硬约束:等值(Equality)→ 排序(Sort)→ 范围(Range)。顺序错一个,后面字段基本失效。
以商品列表页查询为例:
db.products.find({
category: "smartphone",
brand: { $in: ["Apple", "Xiaomi"] },
price: { $gte: 3000, $lte: 8000 },
rating: { $gte: 4.5 }
}).sort({ salesCount: -1, createdAt: -1 })
对应最优索引:
db.products.createIndex({
category: 1, // 等值,高选择性,放最前
brand: 1, // 等值,$in 也视为等值匹配
salesCount: -1, // 排序字段(第一个 sort)
createdAt: -1, // 排序字段(第二个 sort)
price: 1, // 范围,放最后
rating: 1 // 范围,但注意:rating 和 price 都是 range,只能保证其中一个高效过滤
})
关键点:
- 不要把
price放brand前面——brand是等值,price是范围,前置范围字段会切断最左前缀 -
$in在复合索引中等效于多个等值查询,仍走索引前缀 - 两个 range 字段(如
price和rating)同时存在时,MongoDB 只能对第一个做索引范围扫描,第二个靠内存过滤
数组字段(如 attributes)必须单独建路径复合索引
电商常把规格存为数组:attributes: [{name: "color", value: "red"}, {name: "storage", value: "256GB"}]。直接查 {"attributes.name": "color", "attributes.value": "red"} 不会命中普通索引。
必须建:
db.products.createIndex({ "attributes.name": 1, "attributes.value": 1 })
且只在 MongoDB 4.2+ 有效。低版本只能建多键索引,无法支持该组合查询。
使用时注意:
- 查询必须用等值匹配
name,否则索引失效(如{"attributes.name": {$regex: "^col"}}不走索引) - 避免
$unwind:它会让一个含5个属性的商品文档膨胀成5行,聚合阶段内存暴涨甚至OOM - 高频筛选属性(如 color、storage)建议冗余为独立字段:
color: "red"、storage: "256GB",再建{color: 1, storage: 1}索引,性能更稳
实战中容易被忽略的三个细节
复合索引不是建完就万事大吉:
- 用
db.products.getIndexes()确认索引名和字段名完全一致("Attributes.name"≠"attributes.name") - 索引字段大小写、空格、引号都影响匹配;
explain("executionStats")必须看totalKeysExamined是否接近nReturned - 写入代价真实存在:每多一个复合索引,INSERT/UPDATE 延迟上升约15%,电商核心集合建议控制在3个以内关键索引
真正卡住的从来不是语法,而是索引是否覆盖了你的查询模式、字段顺序是否让B-tree能跳过无效分支、以及数组字段有没有被当作“普通字段”误用。











