技术干货 | MongoDB 功能详解之通配符索引(Wildcard Indexes)

随风康康
发布于 2023-5-22 16:25
浏览
0收藏

通配符索引(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 上的通配符索引可以支持 userMetadatauserMetadata.likesuserMetadata.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中文社区

分类
标签
已于2023-5-22 16:25:05修改
收藏
回复
举报
回复
    相关推荐