【MongoDB】多键索引的边界处理(二)

此文接上篇《多键索引的边界处理(一)

 

二、多键索引的复合边界

复合边界是指对复合索引的多键使用边界。例如,给定一个复合索引{a:1,b:1},其字段a上的边界[ [ 3, ] ],字段b上的边界[ [ -, 6 ] ],则将边界复合会导致使用两个界限:

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

如果MongoDB无法将这两个边界组合在一起,则MongoDB始终通过其前导字段的边界来限制索引扫描,在这种情况下,a: [ [ 3, ] ]

1.数组字段上的复合索引

考虑一个复合多键索引(其中一个索引字段是数组的复合索引)。例如,一个集合survey包含带有一个字段 item 和一个数组字段ratings的文档:

{ _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, ] ]

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

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

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

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

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

标量类型(Scalar type)是相对复合类型(Compound type)来说的:标量类型只能有一个值,而复合类型可以包含多个值。复合类型是由标量类型构成的。
在C语言中,整数类型(int、short、long等)、字符类型(char、wchar_t等)、枚举类型(enum)、小数类型(float、double等)、布尔类型(bool)都属于标量类型,一份标量类型的数据只能包含一个值。例如:
int n = 100;

n 就表示100这一个整数。当然,n的值可以被改变,但无论如何,都只能表示一个值。
结构体(struct)、数组都属于复合类型,一份复合类型的数据可以包含多个标量类型的值,也可以包含其他复合类型的值。例如:
int nums[ ] = { 5, 38, 49, 92 };

在版本3.4中进行了更改:仅对于WiredTiger和内存中存储引擎,

从MongoDB 3.4开始,对于使用MongoDB 3.4或更高版本创建的多键索引,MongoDB跟踪一个或多个多键索引字段。跟踪此信息使MongoDB的查询索引边界更严格。

上述复合索引位于标量字段 item和数组字段ratings上:

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

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

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

db.survey.find( {

   item: { $gte: "L", $lte: "Z"}, ratings : { $elemMatch: { $gte: 3, $lte: 6 } }

} )

MongoDB将把item的边界相交后为[ [ "L", "Z" ] ] ,并将ratings的边界相交后为[[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的早期版本无法将这些范围组合为标量字段。

 

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

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

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

score 字段的附点字段名称为“ ratings.score”。

 

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

考虑一个集合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 } 的范围是 [ [ -, 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。有关更多信息,请参见数组中索引字段的复合边界

 

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

要将来自同一数组的索引键的边界复合在一起:

  1. 索引键必须共享相同的字段路径,但不能超过字段名称,并且
  2. 查询必须指定字段上的谓词在该路径上使用$ elemMatch

对于嵌入式文档中的字段,附点字段名称(例如“ a.b.c.d”)是d的字段路径。要复合来自同一数组的索引键的边界,$ 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 } 的范围是 [ -, 5 ];
  • 谓词 by: "anon" 的范围是 [ "anon", "anon" ]

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

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

 

6.没有$ elemMatch的查询

如果查询未使用$ elemMatch联接索引数组字段中的条件,则MongoDB无法复合其边界。考虑以下查询:

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

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

{

  "ratings.score": [ [ -∞, 5 ] ],

  "ratings.by": [ [ MinKey, MaxKey ] ]

}

7.$ elem匹配不完整的路径

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

例如,集合Survey3包含具有字段项和数组字段等级的文档:

{

  _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”共享字段路径“ 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 } } } )

 

上一篇:多键索引

下一篇:文本索引

原文:https://docs.mongodb.com/manual/core/multikey-index-bounds/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值