首页/数据/NoSQL 速查/通配符索引覆盖未知字段
MongoDB99 条

通配符索引覆盖未知字段

字段名不固定的子文档可建通配符索引 { "attrs.$**": 1 },为其中任意标量字段提供等值与范围加速。

NoSQL 速查数据更新于 2026-10-13

是什么

通配符索引(wildcard index)用 $** 表示「子文档下所有字段」,一句话为字段名事先不确定的结构建立索引:{ "attrs.$**": 1 } 会为 attrs 里的每个标量字段各生成索引条目,于是 { "attrs.color": "red" } 这类查询也能走索引。它解决的是「键名动态但值类型一致」的建模方式带来的查询难题。

基本信息

创建db.products.createIndex({ "attrs.$**": 1 })
全文档范围{ "$**": 1 } 覆盖整份文档的所有标量字段
版本MongoDB 4.2+;复合通配符索引自 7.0 起支持,例如 { tenantId: 1, "attrs.$**": 1 }
排除字段可用 wildcardProjection 排除不想索引的字段
不适合排序、唯一约束、字段名高度发散的结构

典型使用场景

  • 多租户自定义属性:各租户给商品加自己的属性字段,查询要按属性过滤
  • 埋点与日志:事件参数名随版本变化,用通配符索引让任意参数都能走索引
  • 配置类文档:字段结构由业务方定义、代码不做约束的集合
  • 过渡方案:数据模型尚未定型时先保证查询可用,之后再收敛成显式索引
  • 混合查询:复合通配符索引让租户维度与动态属性同时命中同一索引

创建与验证

创建与验证
db.products.createIndex({ "attrs.$**": 1 })          # 为 attrs 下的所有标量字段建索引
db.products.find({ "attrs.color": "red" }).explain("executionStats")   # 确认走的是 IXSCAN

db.products.createIndex(                              # 排除字段:不想为体积大的字段建索引
  { "$**": 1 },
  { wildcardProjection: { payload: 0, raw: 0 } }      # 0 表示不索引该字段
)

db.logs.createIndex({ tenantId: 1, "event.props.$**": 1 })   # 复合通配符索引(7.0+)
db.products.getIndexes()                              # 核对索引定义与字段范围

能力与限制

维度表现说明
过滤字段名动态支持这正是它的核心价值
索引体积随字段数增长字段名越发散,索引条目越多
排序不适用排序仍需要显式字段的索引
唯一约束不支持需要唯一性时用显式字段索引
数组字段按元素生成条目元素很多时索引会明显膨胀
通配符索引把「字段名不确定」的代价转移到了索引体积上:为每个标量字段都留条目的结果是索引可能比数据还大,写入时维护成本也随之上升。它的定位是过渡与兜底,不是鼓励放弃字段命名规范。
在通配符索引上叠加排序、唯一约束或复杂数组查询时,行为与显式索引不同,且不同版本的限制有差异;动手前先查对应版本的官方限制列表,别按普通索引的经验套用。

易错点

  • 用通配符索引当排序索引:排序需求它满足不了,查询计划会退化
  • 指望它提供唯一性约束:通配符索引不做唯一性校验,业务唯一性要靠显式字段索引或应用层保证
  • 对字段名极度发散的结构建 $**:索引条目数等于所有标量字段数量,写放大和存储开销都会失控
  • 建完之后不验证:以为有通配符索引就一定命中,实际仍可能因为查询写法(如嵌套数组、类型不符)退化

数据来源与参考

  • MongoDB Manual: Wildcard Indexes
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌