技术干货 | MongoDB 功能详解之通配符索引(Wildcard Indexes)
通配符索引(Wildcard Indexes):MongoDB 4.2 版本中的新功能。
为了更高效地查询,MongoDB 支持在单字段或多字段上创建索引。由于 MongoDB 支持动态模式,应用程序可以查询那些无法事先知道名称的字段。
MongoDB 4.2 引入了通配符索引,以支撑针对未知或任意字段的查询。
设计一个应用程序,它在 userMetadata 字段下捕获用户定义的数据,并支持对这些数据进行查询:
{ "userMetadata" : { "likes" : [ "dogs", "cats" ] } }
{ "userMetadata" : { "dislikes" : "pickles" } }
{ "userMetadata" : { "age" : 45 } }
{ "userMetadata" : "inactive" }
管理员(Administrators)希望创建索引来支持对 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 的非通配符索引(non-wildcard index)只能支持对 userMetadata 值的查询。
⚠️ 重要:
通配符索引(Wildcard indexes)不是用来替代传统索引的。有关创建索引的详细信息,请参阅创建支持查询的索引(Create Indexes to Support Your Queries)。有关通配符索引限制的完整文档,请参见通配符索引限制(Wildcard Index Restrictions)。
创建通配符索引
⚠️ 重要:
mongod 的功能兼容性版本(featureCompatibilityVersion)必须是 v4.2 才支撑创建通配符索引。有关设置 fCV 的说明,请参照如何在 MongoDB 6.0 部署中设置特性兼容版本(Set Feature Compatibility Version on MongoDB 6.0 Deployments.)
创建通配符索引,您可以使用 createIndex 数据库命令或它的 mongosh 方法: createIndex() 或 createIndexes()。
在字段上创建通配符索引
为指定字段的值创建索引:
db.collection.createIndex( { "fieldA.$**" : 1 } )
使用这个通配符索引,MongoDB 对 fieldA 的所有值进行索引。如果字段是嵌套的文档或数组,则通配符索引递归到文档/数组中,并存储文档/数组中所有字段的值。
例如,product_catalog 集合中的文档可能包含 product_attributes 字段。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" } )
📒 注意:
特殊路径的通配符索引语法与通配符用法不兼容时,参照 Options for wildcard indexes 获取更多的帮助。
有关示例,参见文档内容:在单个字段路径上创建通配符索引(Create a Wildcard Index on a Single Field Path.)
在所有字段上创建通配符索引
以 "$**"
作为索引的关键字,对文档中所有字段( _id 除外)的值进行索引:
db.collection.createIndex( { "$**" : 1 } )
使用此通配符索引,MongoDB 可以对集合中每个文档的所有字段进行索引。
如果给定的字段是嵌套的文档或数组,则通配符索引递归到文档/数组中,并存储文档/数组中所有字段的值。
有关示例,参见文档内容:在所有字段路径上创建通配符索引(Create a Wildcard Index on All Field Paths)。
📒 注意:
默认情况下,通配符索引忽略 _ id字段。要在通配符索引中包含 _ id 字段,必须在通配符投影文档中显式地包含它。更多有关信息,参见通配符索引(wildcard indexes)的选项(Options for wildcard indexes)。
在多个特定字段上创建通配符索引
对文档中指定多个字段的值创建索引:
db.collection.createIndex(
{ "$**" : 1 },
{ "wildcardProjection" :
{ "fieldA" : 1, "fieldB.fieldC" : 1 }
}
)
使用此通配符索引,MongoDB 对集合中每个文档的指定字段的所有值进行索引。
如果给定的字段是嵌套的文档或数组,则通配符索引递归到文档/数组中,并存储文档/数组中所有字段的值。
📒 注意:
通配符索引不支持在通配符投影文档中混合包含和写排除语句,想对 _ id 字段创建索引,只能显式包含。有关通配符投影(wildcardProjection)的更多信息,参见通配符索引的选项(Options for wildcard indexes)。
有关示例,参见在通配符索引中覆盖指定字段(Include Specific Fields in Wildcard Index Coverage)。
创建排除多个特定字段的通配符索引
对文档中不包括特定字段路径的所有字段创建索引:
db.collection.createIndex(
{ "$**" : 1 },
{ "wildcardProjection" :
{ "fieldA" : 0, "fieldB.fieldC" : 0 }
}
)
如此使用通配符索引,MongoDB 将对集合中的每个文档的所有字段(不包括指定的字段路径)进行索引。
如果给定的字段是嵌套的文档或数组,则通配符索引递归到文档/数组中,并存储文档/数组中所有字段的值。
请参照:从通配符索引覆盖中省略特定字段(Omit Specific Fields from Wildcard Index Coverage)。
📒 注意:
通配符索引不支持在通配符投影(wildcardProjection)文档中混合包含和排除语句,除非显式包含 _ id 字段。有关通配符投影(wildcardProjection)的更多信息,参见通配符索引的选项(Options for wildcard indexes)。
注意事项
通配符索引在任何指定的查询条件中最多只能支持一个字段。有关通配符索引查询的更多信息,参见 Wildcard Index Query/Sort Support。
- mongd 的功能兼容版本(featureCompatibilityVersion)必须是4.2才能创建通配符索引。有关设置 fCV 的说明,参见 Set Feature Compatibility Version on MongoDB 6.0 Deployments。
- 默认情况下,通配符索引省略 _id 字段。要在通配符索引中包含 _id 字段,必须在通配符投影文档 wildcardProjection(即{“ _ id”: 1})中显式包含它。
- 可以在集合(collection)中创建多个通配符索引。
- 通配符索引可以覆盖与集合(collection)中的其他索引相同的字段。
- 通配符索引是稀疏索引(Sparse Indexes),只包含具有索引字段的文档的条目,允许索引字段包含空值。
特性
通配符索引在索引对象(即嵌入式文档 embedded document)或数组字段时具有递归遍历的特性:
- 如果字段是一个对象(object),则通配符索引将递归到该对象的最后一层嵌套,并对其内容进行索引。
- 如果该字段是一个数组,则通配符索引将遍历该数组并对每个元素进行索引:
○ 如果数组中的某个元素是一个对象,则通配符索引将深入到该对象中,以按照上面所述对其内容进行索引。
○ 如果元素是一个数组——即直接嵌入在父数组中的数组——那么通配符索引不会遍历嵌入的数组,而是将整个数组作为单个值进行索引。
- 对于所有其他字段,将基础原语 primitive(非对象/数组)的值记录到索引中。
通配符索引持续遍历任何其他嵌套对象或数组,直到达到一个基本值(即不是对象或数组的字段)。然后对这个基本值以及该字段的完整路径进行索引。
例如:
{
"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 的记录是无序的。通配符索引在将元素写到索引中时忽略数组元素的位置。通配符索引仍然可以支持含有显式数组索引的查询。参照 Queries with Explicit Array Indices 获取详情。
有关嵌套对象的通配符索引行为的更多信息,请参阅:Nested Objects。
有关嵌套数组的通配符索引行为的详细信息,请参阅:Nested Arrays。
嵌套对象(Nested Objects)
当通配符索引遇到嵌套对象时,它会遍历到对象中并对其内容进行索引。例如:
{
"parentField" : {
"nestedField" : "nestedValue",
"nestedArray" : ["nestedElement"]
"nestedObject" : {
"deeplyNestedField" : "deeplyNestedValue"
}
}
}
一个通配符索引,其中包含 parentField,它深入到对象中以遍历和索引其内容:
- 字段本身是一个对象(例如一个嵌入式文档) ,下降到该对象以索引其内容。
- 作为数组的字段,遍历数组并索引其内容。
- 其他字段,将基本元素(非对象/数组)值记录到索引中。
通配符索引继续遍历任何其他嵌套对象或数组,直到达到一个基本值(即不是对象或数组的字段)。然后对该基元值以及该字段的完整路径进行索引。
给定示例文档,通配符索引将以下记录添加到索引中:
-
"parentField.nestedField" : "nestedValue"
-
"parentField.nestedObject.deeplyNestedField" : "deeplyNestedValue"
- "parentField.nestedArray" : "nestedElement"
有关嵌套数组的通配符索引行为的更多信息,请参阅:Nested Arrays。
嵌套数组(Nested Arrays)
当通配符索引遇到嵌套数组时,它尝试遍历数组以索引其元素。如果数组本身是父数组(即嵌入数组)中的一个元素,则通配符索引将整个数组记录为一个值,而不是遍历其内容。例如:
{
"parentArray" : [
"arrayElementOne",
[ "embeddedArrayElement" ],
"nestedObject" : {
"nestedArray" : [
"nestedArrayElementOne",
"nestedArrayElementTwo"
]
}
]
}
一个通配符索引,其中包含 parentField ,它深入到对象中以遍历和索引其内容:
- 字段本身是一个对象(例如一个嵌入式文档) ,下降到该对象以索引其内容。
- 作为数组的字段,遍历数组并索引其内容。
- 其他字段,将基本元素(非对象/数组)值记录到索引中。
通配符索引继续遍历任何其他嵌套对象或数组,直到达到一个基本值(即不是对象或数组的字段)。然后对该基元值以及该字段的完整路径进行索引。
给定示例文档,通配符索引将以下记录添加到索引中:
-
"parentArray" : "arrayElementOne"
-
"parentArray" : ["embeddedArrayElement"]
-
"parentArray.nestedObject.nestedArray" : "nestedArrayElementOne"
- "parentArray.nestedObject.nestedArray" : "nestedArrayElementTwo"
注意,parentField.nestedArray 的记录不包括每个元素的数组位置。通配符索引在将元素记录到索引中时忽略数组元素的位置。通配符索引仍然可以支持包含显式数组索引的查询。请参阅:Queries with Explicit Array Indices
📒 TIP:
另见:BSON 文档的嵌套深度 Nested Depth for BSON Documents
限制条件
- 不能使用通配符索引分片集合。在要分片的一个或多个字段上创建非通配符索引。有关选择碎片键的详细信息,请参阅碎片键(Shard Keys)。
- 不能创建复合索引。
- 不能为通配符索引指定下列属性:
○ TTL
○ Unique
- 不能使用通配符语法创建下列索引类型:
○ 2d(地理空间)
○ 2dsphere(地理空间)
○ Hashed
⚠️ 重要:
通配符索引与通配符文本索引(Wildcard Text Indexes)不同且不兼容。通配符索引不支持使用 $Text 运算符的查询。
有关通配符索引创建限制的完整文档,参见不兼容索引类型或属性(Incompatible Index Types or Properties)。
通配符索引查询/排序支持
覆盖查询
通配符索引支持覆盖查询(covered query),必须满足以下所有条件:
- 满足查询条件的查询计划,可以利用通配符索引。
- 查询条件正好命中通配符索引所覆盖的一个字段。
- 排除了 _ id 字段,只包括查询字段。
- 指定的查询字段不能是数组。
例如 employees 集合上的以下通配符索引:
db.employees.createIndex( { "$**" : 1 } )
以下操作查询单个字段 lastName,并从结果文档中查询出所有其他字段:
db.employees.find(
{ "lastName" : "Doe" },
{ "_id" : 0, "lastName" : 1 }
)
如果 lastName 字段不是一个数组,则 MongoDB 可以使用 $* * 通配符索引来实现覆盖查询。
多字段查询语句
通配符索引最多只能支持一个查询谓词字段,即:
- MongoDB 的查询条件不可以在使用通配符索引时还覆盖其他索引。
- MongoDB 的查询条件不可以同时覆盖两个通配符索引。
- 一个通配符索引可以支持多个字段联合查询,也可以只使用通配符索引包含的其中一个字段查询。
但是,MongoDB 可以使用相同的通配符索引来满足 $or 聚合函数查询 或 $or 操作符查询。
查询排序
满足以下所有条件时,MongoDB 可以使用通配符索引来满足排序 sort ():
- 查询条件对应的查询计划命中通配符索引。
- 只能针对查询条件中的字段 sort () 排序。
- 排序指定的字段不能是数组。
如果不满足上述条件,MongoDB 就不能使用通配符索引进行排序。有关详细信息,请参阅索引交集和排序(Index Intersection and 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()。
不支持的查询模式
- 通配符索引不支持使用文档中不存在字段的查询。
- 通配符索引不支持检查字段是否等于文档或数组的查询条件。
- 通配符索引不支持检查字段是否为空的查询条件。
更多详细信息,参见不支持的查询和聚合模式(Unsupported Query and Aggregation Patterns)
使用显式数组索引的查询
MongoDB 通配符索引在索引期间不记录数组中任何元素的数组位置。
但是,MongoDB 仍然可以选择通配符索引来响应一个查询,该查询包含一个或多个具有显式数组索引的字段路径(例如,parentArray.0.nestedArray.0)。
由于为多层嵌套数组定义索引边界的复杂性日益增加,如果查询中的给定字段路径包含超过 8 个显式数组索引,MongoDB 不会利用通配符索引来响应该路径。
MongoDB 仍然可以使用通配符索引来响应查询中的其他字段路径。
例如:
{
"parentObject" : {
"nestedArray" : [
"elementOne",
{
"deeplyNestedArray" : [ "elementTwo" ]
}
]
}
}
MongoDB 可以选择一个包含 parentObject 的通配符索引来满足以下查询:
-
"parentObject.nestedArray.0" : "elementOne"
- "parentObject.nestedArray.1.deeplyNestedArray.0" : "elementTwo"
如果查询条件中的给定字段路径使用了超过 8 个显式数组索引,MongoDB 不会利用通配符索引来响应该字段路径。
MongoDB 要么选择另一个符合条件的索引来响应查询,要么执行全表扫描。
注意,通配符索引本身并不限制它们遍历文档的深度,这种限制只适用于显式指定精确数组索引的查询。
在没有显式数组索引的情况下,发出相同的查询,MongoDB 可以选择通配符索引来响应查询:
-
"parentObject.nestedArray" : "elementOne
-
"parentObject.nestedArray.deeplyNestedArray" : "elementTwo"
📒 注意:
另见:BSON文档的嵌套深度 Nested Depth for BSON Documents
原文:Wildcard Indexes
关于译者:
赵瑞航,MongoDB 中文社区成员,现就职于中国联通软件研究院,目前专注于 MongoDB 数据库运维与技术支持。
文章转载自公众号: Mongoing中文社区