MongoDB同样支持用户自定义的多个字段组成的索引,称为复合索引。
小贴士
复合索引最大可拥有31个字段。
创建复合索引
创建复合索引的语句格式如下:
db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )
复合索引中的索引类型与单键索引中的索引类型意义大致相同,但是复合索引不能创建hash索引类型。
接下来我们继续对achievement集合做测试,创建的索引如下所示:
db.achievement.createIndex({"student_name" : 1, "score" : -1})
注意,创建索引时字段的顺序是非常重要的,以上面创建索引为例,先是根据student_name进行升序排列,然后再根据student_name中的score进行降序排列。
除了可以匹配所有的索引字段,复合索引还支持索引字段的子集字段的查询匹配,比如说,下面例子都会通过索引进行查询:
db.achievement.find({"student_name" : "student_85"})
db.achievement.find({"student_name" : "student_85", "score" : 85})
索引排序
在单键索引中,由于MongoDB会从任意一个方向对索引数据进行遍历,所以不必过多担心索引排序的问题。但是在复合索引中,需要care这个问题。
为了得出直观的结果,我们将之前创建的索引暂时清除掉,然后重新创建一个如下的索引:
db.achievement.createIndex({"student_name" : "1", "score" : "-1"})
在进行如下查询时,MongoDB是这样解释的:
> db.achievement.find().sort({"student_name" : 1, "score" : -1}).explain()
{
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"student_name" : 1,
"score" : -1
},
"indexName" : "student_name_1_score_-1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"student_name" : [
"[MinKey, MaxKey]"
],
"score" : [
"[MaxKey, MinKey]"
]
}
}
},
...
}
> db.achievement.find().sort({"student_name" : -1, "score" : 1}).explain()
{
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"student_name" : 1,
"score" : -1
},
"indexName" : "student_name_1_score_-1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "backward",
"indexBounds" : {
"student_name" : [
"[MaxKey, MinKey]"
],
"score" : [
"[MinKey, MaxKey]"
]
}
}
},
...
}
两个查询条件均通过索引进行遍历。
但是当执行下述查询时:
> db.achievement.find().sort({"student_name" : 1, "score" : 1}).explain()
{
...
"winningPlan" : {
"stage" : "SORT",
"sortPattern" : {
"student_name" : 1,
"score" : 1
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "COLLSCAN",
"direction" : "forward"
}
}
},
...
}
> db.achievement.find().sort({"student_name" : -1, "score" : -1}).explain()
{
...
"winningPlan" : {
"stage" : "SORT",
"sortPattern" : {
"student_name" : -1,
"score" : -1
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "COLLSCAN",
"direction" : "forward"
}
}
},
...
}
发现并没有遍历索引,而是进行了全局扫描,更多的关于遍历索引排序的问题,请查看这篇文章:使用索引进行排序。
索引前缀
索引前缀指的是索引字段开头部分的子集,比如说,我们创建一个如下的索引:
db.achievement.createIndex({"student_name" : 1, "score" : 1, "address.province" : 1})
则下面这些索引都属于上面这个索引的索引前缀:
{"student_name" : 1}
{"student_name" : 1, "score" : 1}
即使用以上三个形式作为查询条件时,都会通过索引进行遍历。但是,如下情况也会通过索引进行遍历:
{"student_name" : 1, "address.province" : 1}
但是它只会命中{“student_name” : 1}这个索引,{“address.province” : 1}这个部分将会对前面遍历出来的文档进行全局遍历,最后得出需要的结果集。
但是,如下几种查询形式,将不会通过索引进行遍历:
{"score" : 1 }
{"address.province" : 1}
{"score" : 1, "address.province" : 1}
小贴士
如果你有一个集合中拥有形如{“a” : 1, “b” : 1}和{“a” : 1}的索引,如果其中的每一个索引都没有稀疏或者唯一的约束,那么完全可以将{“a” : 1}这个索引删除掉,MongoDB完全支持通过索引前缀对集合的遍历。