通配符索引 (Wildcard Indexes)
MongoDB 支持在一个字段或一组字段上创建索引以支持查询。 由于 MongoDB 支持动态模式,因此应用程序可以查询事先无法知道名称或具有任意名称的字段。
MongoDB 版本中的新增功能:4.2
MongoDB 4.2 引入通配符索引,用于支持对未知或任意字段的查询。
考虑一个应用程序,它捕获 userMetadata
字段下的用户定义数据并支持对该数据进行查询:
{ "userMetadata" : { "likes" : [ "dogs", "cats" ] } } { "userMetadata" : { "dislikes" : "pickles" } } { "userMetadata" : { "age" : 45 } } { "userMetadata" : "inactive" }
管理员希望创建索引以支持对userMetadata
的任何子字段进行查询。
userMetadata
上的通配符索引可以支持对userMetadata
、 userMetadata.likes
、 userMetadata.dislikes
和userMetadata.age
进行单字段查询:
db.userData.createIndex( { "userMetadata.$**" : 1 } )
该索引可以支持以下查询:
db.userData.find({ "userMetadata.likes" : "dogs" }) db.userData.find({ "userMetadata.dislikes" : "pickles" }) db.userData.find({ "userMetadata.age" : { $gt : 30 } }) db.userData.find({ "userMetadata" : "inactive" })
userMetadata
上的非通配符索引只能支持对userMetadata
值的查询。
创建通配符索引
重要
mongod
featureCompatibilityVersion必须为4.2
才能创建通配符索引。 有关设置 FCV 的说明,请参阅在 MongoDB 6.0 部署上设置特征兼容性版本。
您可以使用createIndexes
数据库命令或其 Shell 助手createIndex()
或createIndexes()
创建通配符索引。
在字段上创建通配符索引
要索引特定字段的值,请执行以下操作:
db.collection.createIndex( { "fieldA.$**" : 1 } )
使用此通配符索引,MongoDB 可为fieldA
的所有值编制索引。 如果字段是嵌套文档或数组,则通配符索引将递归到文档/数组中,并将所有字段的值存储在文档/数组中。
product_catalog
例如,product_attributes
collection中的文档可能包含字段。product_attributes
字段可以包含任意嵌套字段,包括嵌入式文档和数组:
{ "product_name" : "Spy Coat", "product_attributes" : { "material" : [ "Tweed", "Wool", "Leather" ] "size" : { "length" : 72, "units" : "inches" } } } { "product_name" : "Spy Pen", "product_attributes" : { "colors" : [ "Blue", "Black" ], "secret_feature" : { "name" : "laser", "power" : "1000", "units" : "watts", } } }
以下操作在 product_attributes
字段上创建一个通配符索引:
db.products_catalog.createIndex( { "product_attributes.$**" : 1 } )
通配符索引可支持对product_attributes
或其嵌入式字段进行任意单字段查询:
db.products_catalog.find( { "product_attributes.size.length" : { $gt : 60 } } ) db.products_catalog.find( { "product_attributes.material" : "Leather" } ) db.products_catalog.find( { "product_attributes.secret_feature.name" : "laser" } )
注意
特定于路径的通配符索引语法与wildcardProjection
选项不兼容。 有关更多信息,请参阅wildcard
索引的选项。
有关示例,请参阅在单个字段路径(Field Path)上创建通配符索引。
对所有字段创建通配符索引
要对文档中所有字段的值(不包括_id
)进行索引,请将"$**"
指定为索引键:
db.collection.createIndex( { "$**" : 1 } )
使用此通配符索引,MongoDB 可为collection中每个文档的所有字段建立索引。如果给定字段是嵌套文档或数组,则通配符索引将递归到文档/数组中,并将所有字段的值存储在文档/数组中。
有关示例,请参阅在所有字段路径(Field Path)上创建通配符索引。
注意
默认情况下,通配符索引会省略_id
字段。要将_id
字段包含在通配符索引中,您必须将其明确包含在wildcardProjection
文档中。 有关更多信息,请参阅wildcard
索引的选项。
在多个特定字段上创建通配符索引
要对文档中多个特定字段的值进行索引,请执行以下操作:
db.collection.createIndex( { "$**" : 1 }, { "wildcardProjection" : { "fieldA" : 1, "fieldB.fieldC" : 1 } } )
使用此通配符索引,MongoDB 可为collection中每个文档的指定字段的所有值建立索引。如果给定字段是嵌套文档或数组,则通配符索引将递归到文档/数组中,并将所有字段的值存储在文档/数组中。
注意
通配符索引不支持在wildcardProjection
文档中混合包含和排除声明,除非显式包含_id
字段。有关wildcardProjection
的详细信息,请参阅wildcard
索引的选项。
有关示例,请参阅在通配符索引覆盖中包含特定字段。
创建排除多个特定字段的通配符索引
要对文档中除特定字段路径(Field Path)之外的所有字段进行索引:
db.collection.createIndex( { "$**" : 1 }, { "wildcardProjection" : { "fieldA" : 0, "fieldB.fieldC" : 0 } } )
使用此通配符索引,MongoDB 可以对collection中每个文档的所有字段进行索引,但不包括指定的字段路径(Field Path)。如果给定字段是嵌套文档或数组,则通配符索引将递归到文档/数组中,并将所有字段的值存储在文档/数组中。
有关示例,请参阅从通配符索引覆盖中省略特定字段。
注意
通配符索引不支持在wildcardProjection
文档中混合包含和排除声明,除非显式包含_id
字段。有关wildcardProjection
的详细信息,请参阅wildcard
索引的选项。
Considerations
通配符索引在任何给定查询谓词中最多支持一个字段。 有关通配符索引查询支持的详细信息,请参阅通配符索引查询/排序支持。
mongod
featureCompatibilityVersion必须为4.2
才能创建通配符索引。 有关设置 FCV 的说明,请参阅在 MongoDB 6.0 部署上设置特征兼容性版本。默认情况下,通配符索引会省略 _id 字段。要将
_id
字段包含在通配符索引中,您必须将其显式包含在wildcardProjection 文档中(即{ "_id" : 1 }
)。您可以在一个集合中创建多个通配符索引。
通配符索引可能涵盖与collection中其他索引相同的字段。
通配符索引是稀疏索引,仅包含具有索引字段的文档的条目,即使索引字段包含空值也是如此。
行为
对对象(即嵌入式文档)或数组的字段进行索引时,通配符索引具有特定行为:
如果字段是对象,则通配符索引会下降到对象中并对其内容进行索引。通配符索引会继续下降到遇到的任何其他嵌入式文档。
如果字段是数组,则通配符索引将遍历数组并对每个元素进行索引:
如果数组中的元素是对象,则通配符索引会下降到对象以索引其内容,如上所述。
如果元素是数组(即直接嵌入到父数组中的数组),则通配符索引不会遍历嵌入式数组,而是将整个数组作为单个值进行索引。
对于所有其他字段,将基元(非对象/数组)值记录到索引中。
通配符索引继续遍历任何其他嵌套对象或数组,直到到达原始值(即不是对象或数组的字段)。 然后,它会为这个原始值以及该字段的完整路径编制索引。
例如,考虑以下文档:
{ "parentField" : { "nestedField" : "nestedValue", "nestedObject" : { "deeplyNestedField" : "deeplyNestedValue" }, "nestedArray" : [ "nestedArrayElementOne", [ "nestedArrayElementTwo" ] ] } }
包含parentField
的通配符索引记录以下条目:
"parentField.nestedField" : "nestedValue"
"parentField.nestedObject.deeplyNestedField" : "deeplyNestedValue"
"parentField.nestedArray" : "nestedArrayElementOne"
"parentField.nestedArray" : ["nestedArrayElementTwo"]
请注意, parentField.nestedArray
的记录不包括每个元素的数组位置。 将元素记录到索引时,通配符索引会忽略数组元素的位置。 通配符索引仍可支持包含显式数组索引的查询。 有关更多信息,请参阅使用显式数组索引的查询。
有关嵌套对象的通配符索引行为的更多信息,请参阅嵌套对象。
有关嵌套数组的通配符索引行为的更多信息,请参阅嵌套数组。
嵌套对象
当通配符索引遇到嵌套对象时,它会深入到该对象并对其内容进行索引。 例如:
{ "parentField" : { "nestedField" : "nestedValue", "nestedArray" : ["nestedElement"] "nestedObject" : { "deeplyNestedField" : "deeplyNestedValue" } } }
包含parentField
的通配符索引深入到对象中,以遍历其内容并对其内容进行索引:
对于本身就是对象(即嵌入式文档)的每个字段,深入到该对象以对其内容进行索引。
对于每个字段(如果是数组),遍历该数组并对其内容进行索引。
对于所有其他字段,将基元(非对象/数组)值记录到索引中。
通配符索引继续遍历任何其他嵌套对象或数组,直到到达原始值(即不是对象或数组的字段)。 然后,它会为这个原始值以及该字段的完整路径编制索引。
根据示例文档,通配符索引会将以下记录添加到索引中:
"parentField.nestedField" : "nestedValue"
"parentField.nestedObject.deeplyNestedField" : "deeplyNestedValue"
"parentField.nestedArray" : "nestedElement"
有关嵌套数组的通配符索引行为的更多信息,请参阅嵌套数组。
嵌套数组
当通配符索引遇到嵌套数组时,它会尝试遍历该数组以对其元素进行索引。 如果数组本身是父数组中的一个元素(即嵌入式数组),则通配符索引会将整个数组记录为一个值,而不是遍历其内容。 例如:
{ "parentArray" : [ "arrayElementOne", [ "embeddedArrayElement" ], "nestedObject" : { "nestedArray" : [ "nestedArrayElementOne", "nestedArrayElementTwo" ] } ] }
包含parentArray
的通配符索引下降到数组中以遍历其内容并对其内容进行索引:
对于每个数组元素(即嵌入式数组),将整个数组索引为一个值。
对于每个属于对象的元素,深入到对象中以遍历并索引其内容。
对于所有其他字段,将基元(非对象/数组)值记录到索引中。
通配符索引继续遍历任何其他嵌套对象或数组,直到到达原始值(即不是对象或数组的字段)。 然后,它会为这个原始值以及该字段的完整路径编制索引。
根据示例文档,通配符索引会将以下记录添加到索引中:
"parentArray" : "arrayElementOne"
"parentArray" : ["embeddedArrayElement"]
"parentArray.nestedObject.nestedArray" : "nestedArrayElementOne"
"parentArray.nestedObject.nestedArray" : "nestedArrayElementTwo"
请注意, parentField.nestedArray
的记录不包括每个元素的数组位置。 将元素记录到索引时,通配符索引会忽略数组元素的位置。 通配符索引仍可支持包含显式数组索引的查询。 有关更多信息,请参阅使用显式数组索引的查询。
索引显示
从 MongoDB 6.3、6.0.5 和 5.0.16 开始,wildcardProjection
字段以其提交的形式存储索引投影。早期版本的服务器可能已将投影以标准化形式存储。
服务器以相同的方式使用索引,但您可能会注意到listIndexes
和db.collection.getIndexes()
命令的输出有所不同。
限制
不能使用通配符索引对collection进行分片。在要分片的一个或多个字段上创建非通配符索引。 有关分片键的选择的更多信息,请参阅片键。
您无法创建复合索引。
不能为通配符索引指定以下属性:
不能使用通配符语法创建以下索引类型:
有关通配符索引创建限制的完整文档,请参阅不兼容的索引类型或属性。
通配符索引查询/排序支持
覆盖查询
仅当 满足以下所有条件时,通配符索引才能支持 涵盖的查询 :
查询规划器选择通配符索引来满足查询谓词。
查询谓词可精确指定通配符索引所涵盖的一个字段。
投影显式排除
_id
,仅包含查询字段。指定的查询字段绝不是数组。
请考虑 employees
集合的以下通配符索引:
db.employees.createIndex( { "$**" : 1 } )
以下操作查询单个字段 lastName
并从结果文档中投影出所有其他字段:
db.employees.find( { "lastName" : "Doe" }, { "_id" : 0, "lastName" : 1 } )
假设指定的lastName
绝不是数组,则 MongoDB 可以使用$**
通配符索引来支持覆盖的查询。
多字段查询谓词
通配符索引最多支持一个查询谓词字段。也就是说:
MongoDB 不能使用非通配符索引来满足查询谓词的一个部分,也不能使用通配符索引来满足另一部分。
MongoDB 不能使用一个通配符索引来满足查询谓词的一个部分,而使用另一个通配符索引来满足另一部分。
即使单个通配符索引可以支持多个查询字段,MongoDB 也可以使用通配符索引仅支持其中一个查询字段。 所有剩余字段都在没有索引的情况下解析。
使用排序查询
仅当 以下所有条件为sort()
true 时,MongoDB 才能使用通配符索引来满足 :
查询规划器选择通配符索引来满足查询谓词。
sort()
仅指定查询谓词字段。指定的字段绝不是数组。
如果不满足上述条件,MongoDB 则无法使用通配符索引进行排序。 MongoDB 不支持需要与查询谓词不同的索引的sort()
操作。 有关详细信息,请参阅索引交集和排序。
请考虑 products
集合的以下通配符索引:
db.products.createIndex( { "product_attributes.$**" : 1 } )
以下操作查询单个字段product_attributes.price
并对该字段进行排序:
db.products.find( { "product_attributes.price" : { $gt : 10.00 } }, ).sort( { "product_attributes.price" : 1 } )
假设指定的price
绝不是数组,则 MongoDB 可以使用product_attributes.$**
通配符索引来同时满足find()
和sort()
。
不支持的查询模式
通配符索引不支持以下查询模式:
检查字段是否存在的查询
检查字段是否等于文档或数组的查询
检查字段是否等于 null 的查询
有关详细信息,请参阅不支持的查询和聚合模式。
具有显式数组索引的查询
MongoDB 通配符索引在索引期间不会记录任何给定元素在数组中的数组位置。 但是,MongoDB 仍可能选择通配符索引来回答包含具有一个或多个显式数组索引(例如parentArray.0.nestedArray.0
)的字段路径(Field Path)的查询。由于为每个连续嵌套数组定义索引边界的复杂性越来越高,因此,如果查询中的给定字段路径(Field Path)包含超过8
显式数组索引,则 MongoDB 不会考虑使用通配符索引来回答该路径。MongoDB 仍可考虑使用通配符索引来回答查询中的其他字段路径(Field Path)。
例如:
{ "parentObject" : { "nestedArray" : [ "elementOne", { "deeplyNestedArray" : [ "elementTwo" ] } ] } }
MongoDB 可以选择包含parentObject
的通配符索引来满足以下查询:
"parentObject.nestedArray.0" : "elementOne"
"parentObject.nestedArray.1.deeplyNestedArray.0" : "elementTwo"
如果查询中的给定字段路径(Field Path)指定了超过 8 个显式数组索引,则 MongoDB 不会考虑使用通配符索引来回答该字段路径(Field Path)。MongoDB 会选择另一个符合条件的索引来回答查询,或者执行collection扫描。
请注意,通配符索引本身在索引文档时对其遍历文档的深度没有任何限制。该限制仅适用于显式指定精确数组索引的查询。 通过在没有显式数组索引的情况下发出相同的查询,MongoDB 可能会选择通配符索引来回答查询:
"parentObject.nestedArray" : "elementOne"
"parentObject.nestedArray.deeplyNestedArray" : "elementTwo"