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