必须为嵌套数组字段建多键索引,路径须完整准确(如"items.price")、顺序正确,复合查询需配合$elemmatch与esr原则;先验证multikey状态,避免无效索引。

要让 MongoDB 快速查出“用户订单中价格大于 200 且状态为 shipped 的商品”,必须为嵌套在数组里的对象字段建对多键索引;路径写错一级、大小写差一个字母、顺序颠倒,explain 里就永远看不到 IXSCAN。
确认数组字段是否已触发 multikey
先检查当前索引是否已被标记为 multikey,避免白建:运行 db.orders.getIndexes(),找到目标索引项,确认其中 "multikey": true 字段存在。若为 false,说明 MongoDB 尚未识别该字段含数组——此时插入一条含非空 【items 数组且至少一个元素有 price 字段】 的样例文档,再执行 db.orders.reIndex()(注意:生产环境慎用,会锁表)。
不验证 multikey 状态就直接优化查询,等于在没通电的马路上调试自动驾驶。
单字段索引:只查数组内某个值时用
方法一:对数组本身建索引db.orders.createIndex({ "items.price": 1 })
MongoDB 自动识别 items 是数组,生成 multikey 索引。该索引支持 { "items.price": { $gt: 200 } } 这类查询。
方法二:对整个数组字段建索引(仅限过滤场景)db.orders.createIndex({ "items": 1 })
这会为每个数组元素生成独立索引条目,体积膨胀快;但若你只做 { items: { $in: ["book", "laptop"] } } 这种等值匹配,它比展开路径更轻量。
⚠️ 错误示范:db.orders.createIndex({ "price": 1 }) —— 路径缺失 items. 前缀,完全无效。
复合索引:$elemMatch 多条件必须配对建
第一步:明确查询语句结构
比如你要找订单中“某一件商品同时满足 price > 200 且 status: "shipped"”,实际写法是:db.orders.find({ items: { $elemMatch: { price: { $gt: 200 }, status: "shipped" } } })
第二步:按字段在文档中的完整路径写索引键db.orders.createIndex({ "items.price": 1, "items.status": 1 })
必须严格使用点号连接全路径,【"items.price" 和 "items.status" 顺序不能颠倒】,否则 $elemMatch 边界无法相交,查询退化为 COLLSCAN。
第三步:外层等值条件放最前(如有)
若你还加了 { userId: "u123" } 这类外层过滤,索引应改为:db.orders.createIndex({ "userId": 1, "items.price": 1, "items.status": 1 })
ESR 原则强制要求:等值字段(userId)→ 排序字段(无)→ 范围字段(price),status 放最后因它在此处是等值,但必须跟在 price 后以满足 $elemMatch 前缀要求。
防坑要点:嵌套层级与索引体积控制
若数组元素本身还含多层嵌套,例如 {"items": [{ "detail": { "specs": { "weight": 1.2 } } }]},索引路径必须写到 "items.detail.specs.weight",缺一级都不行。
超过 10 层嵌套(如 a.b.c.d.e.f.g.h.i.j.k)会让索引构建延迟明显上升,建议用真实数据集跑 db.orders.stats().indexDetails 查看内存占用和扫描耗时后再决定是否保留该索引。
一个索引里只能有一个数组字段参与——如果误建 { "items.price": 1, "tags": 1 } 且两者都是数组,MongoDB 4.2+ 会直接报错 "Multi-key indexes do not support multiple array fields"。











