应使用通配符复合索引处理customFields字段名动态变化的场景,需满足tenantId等固定前缀+customFields嵌套对象且内部键名不统一;创建时tenantId必须在"customFields.$**"之前,验证需通过.explain()确认IXSCAN及indexName。

当你的集合中存在大量结构不固定、customFields类字段名动态变化的文档时,为每个可能的子字段单独建索引既不可维护又容易触达64个索引上限,此时必须用通配符复合索引替代。
确认是否适合使用通配符复合索引
先检查你的数据模式是否符合典型适用场景:tenantId等固定前缀字段 + customFields等嵌套对象,且该对象内部字段名不统一(如有的文档含"phone",有的含"mobile",有的含"contactNumber")。
如果所有文档的customFields下字段名完全一致,比如永远只有"addr"、"name"、"blockId"三个键,那就该直接建标准复合索引{tenantId: 1, "customFields.addr": 1, "customFields.name": 1},而不是通配符索引。
通配符索引只为“字段名不可预知”而生,不是性能最优解——它比目标索引慢,但比建一堆单字段索引强。
创建复合通配符索引
方法一:使用createIndex()命令(推荐)
执行:db.salesData.createIndex({ tenantId: 1, "customFields.$**": 1 }, { name: "tenant_customFields" })
这一步必须确保tenantId写在通配符字段之前,否则查询{tenantId: 123}无法命中索引——MongoDB只对索引前缀匹配生效,【"customFields.$**"不能放在tenantId前面】。
方法二:用runCommand批量建索引(适合脚本化部署)
执行:db.runCommand({ createIndexes: "salesData", indexes: [ { key: { tenantId: 1, "customFields.$**": 1 }, name: "tenant_customFields" } ] })
注意:命令中createIndexes是字符串字面量,不是变量,大小写必须完全一致。
验证索引是否生效
第一步:构造一个典型查询,例如db.salesData.find({ tenantId: 456, "customFields.addr": { $exists: true } })
第二步:在查询末尾追加.explain("executionStats"),观察返回结果中的executionStages.stage字段值。
第三步:若看到"stage" : "IXSCAN"且indexName显示为"tenant_customFields",说明索引已命中;若出现"stage" : "COLLSCAN",则说明查询条件未触发前缀匹配,要检查tenantId是否被写成$eq以外的操作符,或是否漏写了tenantId条件。
第四步:特别注意"nReturned"和"totalDocsExamined"两个数值,如果二者相等且远小于集合总文档数,证明索引过滤有效;如果totalDocsExamined接近总数,说明实际还是全表扫描。











