多键索引范围

在本页面

索引扫描的范围定义了在查询期间要搜索的索引部分。当存在一个索引的多个谓词时,MongoDB 将尝试通过* intersection compounding *组合这些谓词的边界,以产生具有较小边界的扫描。

相交边界以获取多键索引

边界相交是指多个边界的逻辑合取(即AND)。例如,给定两个边界[ [ 3, Infinity ] ][ [ -Infinity, 6 ] ],边界的交集会得出[ [ 3, 6 ] ]

给定一个indexed数组字段,请考虑一个查询,该查询在数组上指定多个谓词并且可以使用multikey index。如果$elemMatch加入谓词,则 MongoDB 可以与multikey index边界相交。

例如,集合survey包含具有字段item和数组字段ratings的文档:

{ _id: 1, item: "ABC", ratings: [ 2, 9 ] }
{ _id: 2, item: "XYZ", ratings: [ 4, 3 ] }

ratings数组上创建一个multikey index

db.survey.createIndex( { ratings: 1 } )

以下查询使用$elemMatch要求该数组包含至少一个与两个条件都匹配的* single *元素:

db.survey.find( { ratings : { $elemMatch: { $gte: 3, $lte: 6 } } } )

将谓词分开:

  • 大于或等于 3 个谓词(即$gte: 3)的边界是[ [ 3, Infinity ] ]

  • 小于或等于 6 个谓词(即$lte: 6)的边界是[ [ -Infinity, 6 ] ]

由于查询使用$elemMatch来连接这些谓词,因此 MongoDB 可以将边界与以下项相交:

ratings: [ [ 3, 6 ] ]

如果查询未“ *”未使用$elemMatch联接数组字段中的条件,则 MongoDB 无法与多键索引范围相交。考虑以下查询:

db.survey.find( { ratings : { $gte: 3, $lte: 6 } } )

该查询在ratings数组中搜索至少一个大于或等于 3 的元素和至少一个小于或等于 6 的元素。由于单个元素不需要同时满足两个条件,因此 MongoDB 不会*与边界相交并且使用[ [ 3, Infinity ] ][ [ -Infinity, 6 ] ]。 MongoDB 不保证选择这两个界限中的哪一个。

多键索引的复合边界

复合边界是指对compound index的多个键使用边界。例如,给定一个复合索引{ a: 1, b: 1 },其边界在[ [ 3, Infinity ] ]的字段a上,在[ [ -Infinity, 6 ] ]的字段b的边界上,将这些边界组合在一起将导致使用两个边界:

{ a: [ [ 3, Infinity ] ], b: [ [ -Infinity, 6 ] ] }

如果 MongoDB 无法将这两个边界组合在一起,则 MongoDB 总是通过其前导字段(在本例中为a: [ [ 3, Infinity ] ])的边界来约束索引扫描。

数组字段上的复合索引

考虑一个复合的多键索引;即compound index,其中索引字段之一是数组。例如,集合survey包含具有字段item和数组字段ratings的文档:

{ _id: 1, item: "ABC", ratings: [ 2, 9 ] }
{ _id: 2, item: "XYZ", ratings: [ 4, 3 ] }

item字段和ratings字段上创建compound index

db.survey.createIndex( { item: 1, ratings: 1 } )

以下查询在索引的两个键上指定一个条件:

db.survey.find( { item: "XYZ", ratings: { $gte: 3 } } )

将谓词分开:

  • item: "XYZ"谓词的范围是[ [ "XYZ", "XYZ" ] ]

  • ratings: { $gte: 3 }谓词的范围是[ [ 3, Infinity ] ]

MongoDB 可以将两个边界组合起来以使用以下组合的边界:

{ item: [ [ "XYZ", "XYZ" ] ], ratings: [ [ 3, Infinity ] ] }

标量索引字段上的范围查询(WiredTiger)

在版本 3.4 中更改:仅适用于 WiredTiger 和内存中存储引擎

从 MongoDB 3.4 开始,对于使用 MongoDB 3.4 或更高版本创建的多键索引,MongoDB 会跟踪哪个索引字段或哪些字段导致索引成为多键索引。跟踪此信息使 MongoDB 查询引擎可以使用更严格的索引范围。

上面提到的compound index在标量字段[1] item和数组字段ratings上:

db.survey.createIndex( { item: 1, ratings: 1 } )

对于 WiredTiger 和内存中存储引擎,如果查询操作在 MongoDB 3.4 或更高版本中创建的复合多键索引的索引标量字段上指定多个谓词,则 MongoDB 将与该字段的边界相交。

例如,以下操作在标量字段上指定范围查询以及在数组字段上指定范围查询:

db.survey.find( {
   item: { $gte: "L", $lte: "Z"}, ratings : { $elemMatch: { $gte: 3, $lte: 6 } }
} )

MongoDB 将与item[ [ "L", "Z" ] ]的边界和[[3.0, 6.0]]的评级相交,以使用以下组合的边界:

"item" : [ [ "L", "Z" ] ], "ratings" : [ [3.0, 6.0] ]

再举一个例子,考虑标量字段属于嵌套文档的位置。例如,集合survey包含以下文档:

{ _id: 1, item: { name: "ABC", manufactured: 2016 }, ratings: [ 2, 9 ] }
{ _id: 2, item: { name: "XYZ", manufactured: 2013 },  ratings: [ 4, 3 ] }

在标量字段"item.name""item.manufactured"和数组字段ratings上创建复合多键索引:

db.survey.createIndex( { "item.name": 1, "item.manufactured": 1, ratings: 1 } )

考虑以下在标量字段上指定查询谓词的操作:

db.survey.find( {
   "item.name": "L" ,
   "item.manufactured": 2012
} )

对于此查询,MongoDB 可以使用以下组合范围:

"item.name" : [ ["L", "L"] ], "item.manufactured" : [ [2012.0, 2012.0] ]

MongoDB 的早期版本无法将这些范围组合为标量字段。

[1]标量字段是其值既不是文档也不是数组的字段。例如一个值为字符串或整数的字段是标量字段。


标量字段可以是嵌套在文档中的字段,只要该字段本身不是数组或文档即可。例如,在文档{ a: { b: { c: 5, d: 5 } } }中,cd是标量字段,而ab不是。

标量索引字段(MMAPv1)上的范围查询

对于 MMAPv1 存储引擎,即使查询仅在标量字段上,MongoDB 也不能为复合多键索引的标量字段组合边界。

嵌入式文档数组中字段的复合索引

如果数组包含嵌入式文档,则要在嵌入式文档中包含的字段上构建索引,请使用索引规范中的虚线字段名称。例如,给定以下嵌入式文档数组:

ratings: [ { score: 2, by: "mn" }, { score: 9, by: "anon" } ]

score字段的虚线字段名称是"ratings.score"

非数组字段和数组字段的复合界

考虑集合survey2包含具有字段item和数组字段ratings的文档:

{
  _id: 1,
  item: "ABC",
  ratings: [ { score: 2, by: "mn" }, { score: 9, by: "anon" } ]
}
{
  _id: 2,
  item: "XYZ",
  ratings: [ { score: 5, by: "anon" }, { score: 7, by: "wv" } ]
}

在非数组字段item以及数组ratings.scoreratings.by的两个字段上创建一个compound index

db.survey2.createIndex( { "item": 1, "ratings.score": 1, "ratings.by": 1 } )

以下查询在所有三个字段上指定一个条件:

db.survey2.find( { item: "XYZ",  "ratings.score": { $lte: 5 }, "ratings.by": "anon" } )

将谓词分开:

  • item: "XYZ"谓词的范围是[ [ "XYZ", "XYZ" ] ]

  • score: { $lte: 5 }谓词的范围是[ [ -Infinity, 5 ] ]

  • by: "anon"谓词的范围是[ "anon", "anon" ]

MongoDB 可以根据查询谓词和索引键值将item键的边界与"ratings.score"的边界或"ratings.by"的边界混合。 MongoDB 不保证与item字段合并的范围。例如,MongoDB 将选择将item边界与"ratings.score"边界复合:

{
  "item" : [ [ "XYZ", "XYZ" ] ],
  "ratings.score" : [ [ -Infinity, 5 ] ],
  "ratings.by" : [ [ MinKey, MaxKey ] ]
}

或者,MongoDB 可以选择将item界限与"ratings.by"界限混合:

{
  "item" : [ [ "XYZ", "XYZ" ] ],
  "ratings.score" : [ [ MinKey, MaxKey ] ],
  "ratings.by" : [ [ "anon", "anon" ] ]
}

但是,要将"ratings.score"的边界与"ratings.by"的边界进行复合,查询必须使用$elemMatch。有关更多信息,请参见数组中索引字段的复合边界

数组中索引字段的复合边界

要将来自同一数组的索引键的范围组合在一起:

  • 索引键必须共享相同的字段路径,但不包括字段名称,并且

  • 查询必须在该路径上使用$elemMatch在字段上指定谓词。

对于嵌入式文档中的字段,虚线字段名称(例如"a.b.c.d")是d的字段路径。要复合来自同一数组的索引键的边界,$elemMatch必须位于最多但不包括字段名称本身的路径上;即"a.b.c"

例如,在ratings.scoreratings.by字段上创建compound index

db.survey2.createIndex( { "ratings.score": 1, "ratings.by": 1 } )

字段"ratings.score""ratings.by"共享字段路径ratings。以下查询在字段ratings上使用$elemMatch,以要求该数组包含至少一个与两个条件都匹配的* single *元素:

db.survey2.find( { ratings: { $elemMatch: { score: { $lte: 5 }, by: "anon" } } } )

将谓词分开:

  • score: { $lte: 5 }谓词的范围是[ -Infinity, 5 ]

  • by: "anon"谓词的范围是[ "anon", "anon" ]

MongoDB 可以将两个边界组合起来以使用以下组合的边界:

{ "ratings.score" : [ [ -Infinity, 5 ] ], "ratings.by" : [ [ "anon", "anon" ] ] }

没有$ elemMatch 的查询

如果查询不“ *”使用$elemMatch来加入索引数组字段中的条件,则 MongoDB“不能”将其边界进行复合。考虑以下查询:

db.survey2.find( { "ratings.score": { $lte: 5 }, "ratings.by": "anon" } )

由于数组中的单个嵌入式文档不需要同时满足这两个条件,因此 MongoDB 不会*限制边界。使用复合索引时,如果 MongoDB 无法约束索引的所有字段,则 MongoDB 始终会约束索引的前导字段,在这种情况下为"ratings.score"

{
  "ratings.score": [ [ -Infinity, 5 ] ],
  "ratings.by": [ [ MinKey, MaxKey ] ]
}

$ elem 匹配不完整的路径

如果查询未在嵌入式字段的路径上指定$elemMatch(最多但不包括字段名),则 MongoDB 不能 组合来自同一数组的索引键的范围。

例如,集合survey3包含具有字段item和数组字段ratings的文档:

{
  _id: 1,
  item: "ABC",
  ratings: [ { scores: [ { q1: 2, q2: 4 }, { q1: 3, q2: 8 } ], loc: "A" },
             { scores: [ { q1: 2, q2: 5 } ], loc: "B" } ]
}
{
  _id: 2,
  item: "XYZ",
  ratings: [ { scores: [ { q1: 7 }, { q1: 2, q2: 8 } ], loc: "B" } ]
}

ratings.scores.q1ratings.scores.q2字段上创建一个compound index

db.survey3.createIndex( { "ratings.scores.q1": 1, "ratings.scores.q2": 1 } )

字段"ratings.scores.q1""ratings.scores.q2"共享字段路径"ratings.scores",并且$elemMatch必须在该路径上。

但是,以下查询使用$elemMatch而不在必需的路径上使用:

db.survey3.find( { ratings: { $elemMatch: { 'scores.q1': 2, 'scores.q2': 8 } } } )

因此,MongoDB 不能使边界复杂化,并且在索引扫描期间"ratings.scores.q2"字段将不受约束。要增加界限,查询必须在路径"ratings.scores"上使用$elemMatch

db.survey3.find( { 'ratings.scores': { $elemMatch: { 'q1': 2, 'q2': 8 } } } )