嵌套对象字段建索引必须用点号路径写法,如{"contact.email": 1},直接对整个嵌套对象(如{contact: 1})建索引仅支持字面量完全匹配,无法支持子字段查询;数组内嵌套对象需配合$elemMatch确保多条件命中同一子文档。

嵌套对象字段能建索引,但必须用点号路径写法
MongoDB 支持对嵌套对象里的子字段建索引,比如 {"contact.email": 1} 或 {"address.city": 1},但不能直接对整个嵌套对象字段(如 contact)建“对象级索引”。如果你写 db.users.createIndex({contact: 1}),MongoDB 会把它当作一个整体值索引——只匹配 contact 字段完全相等的文档,无法支持 contact.email = "x" 这类查询。
常见错误现象:查询 { "contact.email": "a@b.com" } 却没走索引,explain() 显示 "stage": "COLLSCAN" —— 很可能就是索引没建在点路径上。
- 正确建法:
db.users.createIndex({"contact.email": 1}) - 错误建法:
db.users.createIndex({contact: 1})(除非你真要查整个 contact 对象字面量相等) - 复合索引中也一样:
db.users.createIndex({"contact.email": 1, "status": 1})有效;{contact: 1, status: 1}无效于子字段查询
平面字段索引更直接,但牺牲了数据局部性与原子性
把 email、city 等字段提一层变成平面结构(如 {"email": "...", "city": "..."}),索引创建毫无歧义:db.users.createIndex({email: 1}) 直接生效。但这会带来三个实际代价:
- 数据冗余风险:如果一个用户有多个联系人,平面结构要么展开成多文档(一对多变多对一),要么丢失结构语义
- 更新非原子:修改
email和phone需两次独立写操作,中间状态可能不一致 - 读取发散:原本一次磁盘 I/O 能拿到完整联系信息,现在可能要跨多个字段甚至多个文档拼装
所以“利于建索引”不等于“更适合业务”,得看查询模式是否真以单字段高频过滤为主,还是更依赖组合条件或嵌套语义。
数组内嵌套对象的索引行为更复杂,$elemMatch 是关键开关
当字段是对象数组(如 addresses: [{city: "Beijing", type: "home"}, {...}]),情况升级:MongoDB 会自动创建多键索引(multikey),但默认只支持“数组中任一元素满足条件”的语义。例如:
-
db.users.find({"addresses.city": "Beijing"})→ 可用{"addresses.city": 1}索引 -
db.users.find({"addresses.city": "Beijing", "addresses.type": "work"})→ 可能误匹配:一个元素含 city,另一个含 type,但不是同一个对象 - 要确保是同一子文档同时满足两个条件,必须用
$elemMatch:{"addresses": {$elemMatch: {city: "Beijing", type: "work"}}}
注意:$elemMatch 查询能否走索引,取决于索引是否建在数组字段本身(如 {"addresses": 1})或其子字段(如 {"addresses.city": 1})。实测中,{"addresses.city": 1} + $elemMatch 组合在 6.0+ 版本能命中索引,但旧版本可能退化为扫描。
性能差异主要来自查询模式,而非字段扁平与否本身
索引效率高低,最终取决于你的 find() 条件是否匹配索引前缀,以及是否触发排序/内存限制。比如:
- 查
{"contact.email": "x", "status": "active"}→ 建{"contact.email": 1, status: 1}复合索引最优 - 查
{"status": "active"}排序contact.email→ 索引顺序很重要:{status: 1, "contact.email": 1}才能避免内存排序(否则可能因超过 32MB 触发SortStage失败) - 嵌套过深(如
a.b.c.d.e)不会让索引变慢,但会让查询语句和索引定义更易出错,维护成本上升
真正容易被忽略的是:多键索引在数组字段上会显著增大索引体积,尤其当每个文档有几十个地址时;而平面字段虽索引轻量,却可能让应用层逻辑变得脆弱——比如忘记校验 email 和 address 是否属于同一上下文。


















