如果要索引数组类型的字段,MongoDB可以在数组每个元素上创建索引。这种多键索引可以有效的支持数组元素查询。多键索引建立在具体的值(比如字符串、数字)或内嵌文档的数组上。
一、创建多键索引
创建多键索引的语法:db.collection.createIndex()
db.coll.createIndex({
MongoDB会自动在数组字段上面创建多键索引,不需要我们做特别的声明
二、索引边界
如果是多键索引,索引边界的计算需要遵循特殊的规则
索引的扫描边界定义了查询过程中索引查找的范围。当多个查询的条件字段在索引中存在时,MongoDB将会使用交集或者并集来判断这些条件字段的边界最终产生一个最小的扫描边界。
1、多键索引的交集边界
交集边界是多个边界的逻辑相交,例如:给定两个边界:【3,+∞)和(-∞,6】,则两个边界相交的结果就是【3,6】
对于给定的数组字段,假定一个查询使用了数组的多个条件字段并且可以使用多键索引。如果使用了$elemMatch连接了条件字段,则MongoDB将会相交多键索引边界
例如:一个survey集合包含的文档里面有一个item字段和一个ratings数组字段
{ _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的查询条件
小于等于6的查询条件
因为查询使用了$elemMatch来连接这两个查询条件,所以MongoDB会相交这两个边界得到:
ratings:[[3,6]]
如果查询没有使用$elemMatch来连接数组字段,则MongoDB不会相交多键边界。看下面的查询:
db.survey.find( { ratings : { $gte: 3, $lte: 6 } } )
这个查询查找的是ratings数组的元素里面至少有一个元素大于等于3且至少有一个元素小于等于6.因为单个元素不需要同时匹配这两个条件,所以MongoDB不需要取【3,+∞)或(-∞,6】的交集,使用者两个边界中的一个就行了。MongoDB没有承诺将会选择这两个边界的哪一个。
对于在具体的值上声明的特定条件的查询,MongoDB不会使用组合多键索引来连接具体值字段的边界,即使查询的条件就是具体的值。
2、多键索引的并集
并集常常用在确定多键组合索引的边界。例如:给定的组合索引{a:1,b:1},在字段a上有一个边界:【3,+∞),在字段b上有一个边界:(-∞,6】,相并这两个边界的结果是:
{ a: [ [ 3, Infinity ] ], b: [ [ -Infinity, 6 ] ] }
如果MongoDB没法并集这两个边界,MongoDB将会强制使用索引的第一个字段的边界来进行索引扫描,在这种情况下就是: a: [ [ 3, Infinity ] ]
3、数组字段的组合索引
假如一个组合多键索引,比如一个组合索引的索引字段是数组,
例子::一个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 } )
下面的查询条件声明了索引包含的两个key:
db.survey.find( { item: "XYZ", ratings: { $gte: 3 } } )
分别处理查询条件:
MongoDB使用并集边界来组合这两个边界:
{ item: [ [ "XYZ", "XYZ" ] ], ratings: [ [ 3, Infinity ] ] }
4、内嵌文档的数组上建立组合索引
如果数组包含的是内嵌文档,想在包含的内嵌文档字段上建立索引,需要在索引声明中使用逗号来分隔字段名。
例如:给定的内嵌文档数组:
ratings: [ { score: 2, by: "mn" }, { score: 9, by: "anon" } ]
则score的逗号字段名称就是:ratings.score
5、在不是数组类型的字段和数组类型字段上进行并集
假设有一个survey2集合包含如下文档:
{ _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" } )
分别对查询条件进行处理:
MongoDB可以组合 item键的边界与 ratings.score和ratings.by两个边界中的一个,到底是score还是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。
6、数组字段索引的并集边界
在数组内部并集索引键的边界:
对于内嵌的文档,使用逗号分隔的路径,比如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。下面的查询使用$elemMatch则要求ratings字段必须包含一个元素匹配这两个条件:
db.survey2.find( { ratings: { $elemMatch: { score: { $lte: 5 }, by: "anon" } } } )
分别对查询条件进行处理:
MongoDB可以使用并集边界来组合这两个边界:
{ "ratings.score" : [ [ -Infinity, 5 ] ], "ratings.by" : [ [ "anon", "anon" ] ] }
7、不使用$elemMatch进行查询
如果不使用$elemMatch来连接数组索引字段的条件,MongoDB不会并集他们的边界。看如下的查询:
db.survey2.find( { "ratings.score": { $lte: 5 }, "ratings.by": "anon" } )
因为一个数组内嵌文档不需要都匹配这两个规则,MongoDB不会并集他们的边界。当使用到组合索引的时候,如果MongoDB不能使用索引的所有字段,mongod会使用排在前面的索引字段,比如这个例子的ratings.score
{ "ratings.score": [ [ -Infinity, 5 ] ], "ratings.by": [ [ MinKey, MaxKey ] ] }
8、不完整的路径上使用$elemMatch
如果查询语句没有在内嵌文档字段的路径上使用$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.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.score.q2不会使用索引。为了并集边界,查询必须在路径ratings.scores上使用$elemMatch:
db.survey3.find( { 'ratings.scores': { $elemMatch: { 'q1': 2, 'q2': 8 } } } )
三、限制
1、组合多键索引
对于一个组合多键索引,每个索引文档最多只能有一个索引字段的值是数组。如果组合多键索引已经存在了,不能在插入文档的时候违反这个限制。
例如:假设有个集合包含如下文档:
{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }
不能在{a:1,b:1}上创建组合多键索引,因为a,b都是数组
再看下面的文档:
{ _id: 1, a: [1, 2], b: 1, category: "A array" } { _id: 2, a: 1, b: [1, 2], category: "B array" }
允许在上面的各个文档上面创建多键索引{ a: 1, b: 1 },因为只有一个字段是数组类型。没有文档同时是数组。当创建这个组合多键索引之后,如果想插入一个文档包含A,B两个数组的话,那么就会插入失败。
2、分片键
不能声明一个多键索引作为分片键索引
然而如果分片键索引是一个组合索引的前缀,如果其他的键(例如这些键不是分片键的一部分)中的一个在数组上建立了索引,则这个组合索引可以变成组合多键索引。组合多键索引对性能有影响。
3、哈希索引
哈希索引不能拥有多键
4、覆盖查询
多键索引不能进行覆盖查询
5、把数组作为一个整体进行查询
当一个查询声明把数组整体作为精确匹配的时候,MongoDB可以使用多键索引来查找这个查询数组的第一个元素,但是不能使用多键索引扫描来找出整个数组。代替方案是当使用多键索引查询出数组的第一个元素之后,MongoDB再对过滤之后的文档再进行一次数组匹配
例子:inventory集合包含如下的文档:
{ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] } { _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] } { _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] } { _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] } { _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }
集合上面建立了一个多键索引:
db.inventory.createIndex( { ratings: 1 } )
下面的查询是查找文档的ratings是数组【5,9】:
db.inventory.find({ratings:[5,9]})
MongoDB可以使用多键索引找到文档中包含5的ratings数组,然后MongoDB再检索这些文档中的数组等于数组【5,9】
例子:
1、索引基本数组
假如survey有如下的文档
{ _id: 1, item: "ABC", ratings: [ 2, 5, 9 ] }
在字段ratings上面创建索引:
db.survery.createIndex({ratings:1})
因为ratings字段是一个数组,所以该索引是一个多键索引,该多键索引包含下面三个索引键,每个都指向相同的文档:
2、索引数组内嵌文档
可以在数组字段的内嵌对象上创建多键索引
假如inventory集合有如下文档:
{ _id: 1, item: "abc", stock: [ { size: "S", color: "red", quantity: 25 }, { size: "S", color: "blue", quantity: 10 }, { size: "M", color: "blue", quantity: 50 } ] } { _id: 2, item: "def", stock: [ { size: "S", color: "blue", quantity: 20 }, { size: "M", color: "blue", quantity: 5 }, { size: "M", color: "black", quantity: 10 }, { size: "L", color: "red", quantity: 2 } ] } { _id: 3, item: "ijk", stock: [ { size: "M", color: "blue", quantity: 15 }, { size: "L", color: "blue", quantity: 100 }, { size: "L", color: "red", quantity: 25 } ] }
下面的操作将会在stock.size和stock.quantity上面创建多键索引:
db.inventory.createIndex({"stock.size":1,"stock.quantity":1})
这个组合多键索引支持查询条件中包含这两个索引字段,也支持仅仅包含字段:stock.size的查询。见下面的例子中:
db.inventory.find( { "stock.size": "M" } ) db.inventory.find( { "stock.size": "S", "stock.quantity": { $gt: 20 } } )
组合多键索引也支持排序操作,见下例:
db.inventory.find( ).sort( { "stock.size": 1, "stock.quantity": 1 } ) db.inventory.find( { "stock.size": "M" } ).sort( { "stock.quantity": 1 } )