多键索引边界
索引扫描的边界定义了查询期间要搜索的索引部分。 当索引上存在多个谓词时,MongoDB 将尝试通过交集或复合来组合这些谓词的边界,以便生成具有较小边界的扫描。
多键索引的相交边界
边界交集是指逻辑合取(即 AND
) 的多个边界。 实例,给定两个边界[ [ 3, Infinity ] ]
和[ [ -Infinity, 6 ] ]
,边界的交集结果为[ [ 3, 6 ] ]
。
给定一个索引大量字段,考虑一个在大量上指定多个谓词并且可以使用多键索引的查询。 如果 连接谓词,则MongoDB可以与$elemMatch
多键索引 边界相交。
例如,创建一个survey
集合,其中包含具有字段item
和数组字段ratings
的文档:
db.survey.insertMany( [ { _id: 1, item: "ABC", ratings: [ 2, 9 ] }, { _id: 2, item: "XYZ", ratings: [ 4, 3 ] } ] )
在ratings
数组上创建多键索引:
db.survey.createIndex( { ratings: 1 } )
以下查询使用$elemMatch
要求数组中至少包含一个同时匹配这两个条件的元素:
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 不保证它会选择这两个边界中的哪一个。
多键索引的复合边界
复合边界是指对复合索引的多个键使用边界。 实例,给定一个复合索引{ a: 1, b: 1 }
,其[ [
3, Infinity ] ]
的字段a
上的边界和[ [ -Infinity, 6 ]
]
的字段b
上的边界,复合边界会导致使用两个边界:
{ a: [ [ 3, Infinity ] ], b: [ [ -Infinity, 6 ] ] }
如果 MongoDB 无法复合这两个边界,则 MongoDB 始终将索引扫描限制为其前导字段上的边界,在本例中为a:
[ [ 3, Infinity ] ]
。
数组字段上的复合索引
考虑复合多键索引;即复合索引,其中索引字段之一是数组。 例如,创建一个survey
集合,其中包含具有字段item
和数组字段ratings
的文档:
db.survey.insertMany( [ { _id: 1, item: "ABC", ratings: [ 2, 9 ] }, { _id: 2, item: "XYZ", ratings: [ 4, 3 ] } ] )
在item
字段和ratings
字段上创建复合索引:
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)
仅适用于 WiredTiger 和内存存储引擎,
在多键索引中,MongoDB 会追踪哪个或哪些索引字段导致索引成为多键索引。 跟踪此信息允许 MongoDB 查询引擎使用更严格的索引边界。
上述复合索引位于标量字段[1] item
和数组字段ratings
上:
db.survey.createIndex( { item: 1, ratings: 1 } )
对于 WiredTiger 和内存存储引擎,如果查询操作在复合多键索引的索引标量字段上指定了多个谓词,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
collection:
db.survey.insertMany( [ { _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 } } } 中, c 和d 是标量字段,而a 和b 不是标量字段。 |
嵌入式文档数组中字段的复合索引
如果数组包含嵌入式文档,要对嵌入式文档中包含的字段进行索引,请在索引规范中使用虚线字段名称。 实例,给定以下嵌入式文档数组:
ratings: [ { score: 2, by: "mn" }, { score: 9, by: "anon" } ]
score
字段的虚线字段名称为"ratings.score"
。
非数组字段和数组字段的复合边界
考虑collection 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.score
和ratings.by
上创建复合索引:
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
。 有关更多信息,请参阅数组中索引字段的复合边界。
数组中索引字段的复合边界
要将同一数组中索引键的边界复合在一起,请执行以下操作:
索引键必须共享相同的字段路径(Field Path),但不包括字段名称,并且
查询必须在该路径上使用
$elemMatch
指定字段的谓词。
对于嵌入式文档中的字段,虚线字段名称(例如"a.b.c.d"
)是d
的字段路径(Field Path)。要复合来自同一数组的索引键的边界, $elemMatch
必须位于直至字段名本身但不包括字段名本身的路径上;即"a.b.c"
。
实例,在ratings.score
和ratings.by
字段上创建复合索引:
db.survey2.createIndex( { "ratings.score": 1, "ratings.by": 1 } )
字段"ratings.score"
和"ratings.by"
共享字段路径ratings
。 以下查询在字段ratings
上使用$elemMatch
,以要求数组至少包含一个同时匹配这两个条件的单个元素:
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 ] ] }
$elemMatch
在不完整路径上
如果查询未在嵌入式字段的路径上指定$elemMatch
(最多但不包括字段名称),则 MongoDB无法复合来自同一数组的索引键的边界。
例如,collection 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.q1
和ratings.scores.q2
字段上创建复合索引:
db.survey3.createIndex( { "ratings.scores.q1": 1, "ratings.scores.q2": 1 } )
字段"ratings.scores.q1"
和"ratings.scores.q2"
共享字段路径(Field Path)"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 } } } )