MongoDB对数组字段建索引默认生成多键索引,仅高效支持“数组包含某值”查询;查整个数组相等需冗余哈希字段或拆分为独立字段,查嵌套对象需对具体路径建复合索引并配合$elemMatch使用。

直接说结论:对数组字段建索引时,MongoDB 默认创建的是多键索引(multikey),但“精准”取决于你查的是整个数组、某个元素,还是满足条件的元素——必须按查询模式选对索引结构,否则可能全表扫描或命中率极低。
为什么 db.collection.createIndex({ tags: 1 }) 不总管用?
MongoDB 对数组字段自动启用 multikey 索引,但它只保证能高效匹配“数组中存在某个值”,不保证能精确匹配整个数组顺序或长度。比如:
- 查询
{ tags: ["a", "b"] }(整个数组相等)→ 普通{ tags: 1 }索引无效,会走 COLLSCAN - 查询
{ tags: "a" }(包含 a)→{ tags: 1 }有效,且是 multikey - 查询
{ tags: { $all: ["a", "b"] } }→ 同样依赖{ tags: 1 },但需注意顺序无关性
根本原因:MongoDB 把数组展开为多个索引条目(如 ["a","b"] → 索引存 "a" 和 "b" 两条),无法还原原始数组结构。
查整个数组相等?必须用 arrayElemAt + 复合索引或冗余字段
原生不支持对整个数组内容做精确等值索引。可行路径只有两个:
- 加冗余字段存数组的稳定哈希(如 SHA-256),再对这个字段建索引:
{ tags_hash: 1 },写入时同步计算并更新 - 如果数组长度固定且较短(如 always 2 个标签),可拆成独立字段:
{ tag1: 1, tag2: 1 },再建复合索引;配合$expr查询时用$arrayElemAt提取,但注意$expr无法走索引,只能作为过滤补充
示例冗余方案:
db.items.updateMany(
{},
[{ $set: { tags_hash: { $sha256: { $concat: ["$tags.0", "$tags.1", ...] } } }}]
)
之后查 { tags_hash: "abc123..." } 就能走精确索引。
查数组中满足复杂条件的元素?用 $elemMatch + 字段路径索引
当数组里是对象(如 comments: [{ user: "A", score: 5 }] ),且你要找 “score > 4 且 user 是 A”的那个评论,不能只建 { comments: 1 }。
- 正确做法:对嵌套路径建索引,例如
{ "comments.user": 1, "comments.score": 1 } - 查询必须用
$elemMatch或点号路径:{ "comments.user": "A", "comments.score": { $gt: 4 } } - ⚠️ 注意:如果只建了
{ "comments.user": 1 },而查询带comments.score条件,该条件无法被索引覆盖,仍要扫描
这种索引是 multikey 的,但只对 $elemMatch 或显式点号查询生效;用 { comments: { $elemMatch: { user: "A", score: { $gt: 4 } } } } 才能触发索引下推。
数组索引性能和兼容性容易被忽略的点
multikey 索引有硬限制和隐含开销:
- 一个索引只要有一个字段是数组,整个索引就变成 multikey,即使其他字段是标量——这会影响排序、唯一性约束(
unique: true对数组字段基本不可靠) - 分片集合中,multikey 索引字段不能用作分片键(除非是 hashed 分片键,但也不推荐)
- 使用
$size查询(如{ tags: { $size: 2 } })永远无法走索引,MongoDB 明确不支持 - Aggregation 中
$unwind后再$match数组字段,索引通常失效——应尽量把过滤提前到$match阶段,避免先展开
最常被跳过的动作:运行 db.collection.explain("executionStats").find(...) 看 executionStages 里有没有 IXSCAN,以及 totalDocsExamined 是否接近 nReturned;差太多,说明索引没被用对。











