通配符索引仅适用于字段名不可控的动态场景,应限定路径(如custom.$)而非全局启用;需区分text与1类型索引,复合索引须满足最左前缀原则,且注意内存驻留开销。

通配符索引不能替代合理建模,它只在字段名不可控、结构高度动态的场景下才值得启用——比如用户自定义属性、爬虫抓取的 HTML 元数据、IoT 设备上报的任意传感器字段。盲目加 $** 会拖慢写入、膨胀内存、让查询计划变不可预测。
什么时候该用 customFields.$** 而不是 $**
直接建 db.collection.createIndex({"$**": 1}) 是最偷懒也最危险的做法:它会索引所有字段(包括 _id、__v、嵌套数组里的每个元素),导致索引体积暴增、更新延迟升高、内存压力陡增。
更务实的做法是把动态字段收拢到一个明确路径下,再对那个路径建通配符索引:
- 把用户自定义字段统一存进
custom对象里:{"name":"张三","custom":{"theme":"dark","notify_email":true,"ui_scale":1.2}} - 只对该路径建索引:
db.users.createIndex({"custom.$**": 1}) - 这样既覆盖了
custom.theme、custom.notify_email等任意子字段,又排除了_id、name这类稳定字段的干扰
text 类型和 1 类型的通配符索引完全不兼容
如果你需要全文检索(比如搜索 custom.notes 或 custom.description 里的关键词),必须显式创建通配符文本索引:db.collection.createIndex({"custom.$**": "text"})。它和普通通配符索引互不影响,但也不能混用 —— $** 搭配 "text" 不支持 $eq 查询,搭配 1 又不支持 $text 操作符。
常见错误现象:
- 建了
{"$**": "text"}却想用{custom_tags: "urgent"}做等值查询 → 查不到,因为文本索引不建倒排 term 映射以外的结构 - 建了
{"custom.$**": 1}却执行{$text: {$search: "error"}}→ 报错text index required - 同时建两种通配符索引,导致写入吞吐下降 30%+(实测 Atlas M30 集群)
复合通配符索引中确定字段必须放前面
MongoDB 的索引匹配规则要求:查询条件中,**最左前缀字段必须被命中**,通配符部分才能生效。例如你建了这个索引:{"tenantId": 1, "custom.$**": 1},那么以下查询能走索引:
-
{tenantId: "t-123", "custom.status": "active"}✅ -
{tenantId: "t-123", "custom.priority": {$gt: 5}}✅
但这些不会用上通配符部分:
-
{"custom.status": "active"}❌(缺少tenantId) -
{tenantId: {$in: ["t-123", "t-456"]}, "custom.status": "active"}⚠️($in会让右侧字段失效,实际只用了tenantId部分)
注意:MongoDB 不会自动把 tenantId 和 custom.status 拆成两个独立索引项;它只当整个复合结构存在时才触发通配符扫描逻辑。
通配符索引不包含 _id,但你可以手动加
默认情况下,custom.$** 不会索引 custom._id(即使文档里真有这个字段),也不会索引顶层 _id。如果你确实需要按 custom.id 查,得靠 wildcardProjection 显式声明:
db.collection.createIndex(
{"custom.$**": 1},
{wildcardProjection: {"custom._id": 1}}
)
但要小心:一旦开了 wildcardProjection,所有未列明的字段就都不进索引了——相当于从“通配”退化为“白名单”,失去灵活性。所以除非业务强依赖某个固定子字段,否则别碰这个选项。
真正容易被忽略的是通配符索引的内存驻留行为:它不像普通索引那样只加载热字段页,而是倾向于将整个通配路径的元数据常驻 RAM。在字段爆炸式增长的集合上,这可能悄无声息吃掉 40%+ 的 WiredTiger cache。











