MongoDB中的索引
Posted Buddy Yuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MongoDB中的索引相关的知识,希望对你有一定的参考价值。
在频繁的查询中,索引可以帮助我们提供快速的访问。默认情况下,创建集合后添加一个文档,就会在"_ID"字段上创建一个索引。首先,我们来看看mongodb中的索引吧,我们使用for循环来插入100万的文档。
> for(i=0;i<1000000;i++){db.testindx.insert({"Name":"user"+i,"Age":Math.floor(Math.random()*120)})}
riteResult({ "nInserted" : 1 })
插入100万文档之后,我们来试以下普通查询Name=user999的这个文档,通过explain函数,我们可以查看执行计划.。
> db.testindx.find({"Name":"user999"}).explain("allPlansExecution");
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.testindx",
"indexFilterSet" : false,
"parsedQuery" : {
"Name" : {
"$eq" : "user999"
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"Name" : {
"$eq" : "user999"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 530,
"totalKeysExamined" : 0,
"totalDocsExamined" : 1000000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"Name" : {
"$eq" : "user999"
}
},
"nReturned" : 1,
"executionTimeMillisEstimate" : 400,
"works" : 1000028,
"advanced" : 1,
"needTime" : 1000000,
"needYield" : 26,
"saveState" : 7831,
"restoreState" : 7831,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 1000000
},
"allPlansExecution" : [ ]
}
这里我们可以看到两部分,一部分是queryPlanner,这个相当于关系型数据库当中的执行计划,另外一部分是executionStats,相当于关系型数据库中的统计信息,queryPlanner的stage显示COLLSCAN,这相当于关系型数据库当中的全表扫描。而executionStats中我们可以看到executionTimeMillis为530毫秒。totalDocsExamined为100万,说明查询遍历了100万扫出了结果,不过530毫秒还是很快的。但是为了加快速度,还需要创建索引。这里我们来建立一个索引。使用ensureIndex方法创建单键索引,在Name字段上进行创建。1代表按照Name进行升序,-1则代表按照Name降序。
> db.testindx.ensureIndex({"Name":1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
> db.testindx.find({"Name":"user999"}).explain("allPlansExecution");
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.testindx",
"indexFilterSet" : false,
"parsedQuery" : {
"Name" : {
"$eq" : "user999"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"Name" : 1
},
"indexName" : "Name_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"Name" : [
"[\"user999\", \"user999\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 9,
"totalKeysExamined" : 1,
"totalDocsExamined" : 1,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"Name" : 1
},
"indexName" : "Name_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"Name" : [
"[\"user999\", \"user999\"]"
]
},
"keysExamined" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
},
"allPlansExecution" : [ ]
},
}
创建索引后再次执行刚刚的查询,查看执行计划。这里我们可以看到queryPlanner中的stage变成了IXSCAN,这就相当于关系型数据库当中的索引扫描。而indexName列代表具体使用了哪一个索引,这里我们走了Name_1索引。而在executionStats当中我们可以看到executionTimeMillis下降到了9毫秒,而totalDocsExamined为1行,说明走了索引只扫描了一行就找到了文档。性能得到了巨大提升。
同样的我们也可以和关系型数据库一样创建复合索引。比如我们要在Name字段和Age字段上创建复合索引。
> db.testindx.ensureIndex({"Name":1, "Age": 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
复合索引能够帮助MongoDB更有效的查询,什么时候创建复合索引,这取决于你的查询条件。例如我们的查询条件,第一个字段Name:$1,第二个字段使用了一个范围查询Age:{"$gt":20}。
> db.testindx.find({"Name": "user5","Age":{"$gt":25}}).explain("allPlansExecution")
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.testindx",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"Name" : {
"$eq" : "user5"
}
},
{
"Age" : {
"$gt" : 25
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"Name" : 1,
"Age" : 1
},
"indexName" : "Name_1_Age_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"Name" : [ ],
"Age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"Name" : [
"[\"user5\", \"user5\"]"
],
"Age" : [
"(25.0, inf.0]"
]
}
}
},
"rejectedPlans" : [
{
"stage" : "FETCH",
"filter" : {
"Age" : {
"$gt" : 25
}
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"Name" : 1
},
"indexName" : "Name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"Name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"Name" : [
"[\"user5\", \"user5\"]"
]
}
}
}
]
}
可以看到优化器还是很智能的,winningPlan代表胜利的执行计划,使用了 "indexName" : "Name_1_Age_1"这个组合索引进行过滤,而rejectedPlans则代表拒绝使用"indexName" : "Name_1"这个单列索引进行过滤。
在MongoDB中,我们创建了单键索引和复合索引。而且还指定了索引的排列顺序,1是升序,-1是降序。如果我们使用访问文档进行排序,例如按照Name进行排序查询,那么返回结果也应该和索引一样拥有相同的顺序。如下图所示,就是一个标准查询带排序的操作。
在多个字段进行排序时候,需要创建一个复合索引,在复合索引的字段里面,排序可以是索引的前缀或者是完整的索引排列顺序。索引前缀是复合索引的一个子集,它包含从第一个开始的一个或者多个字段。
举个例子,我们创建Age,Name,Class三个字段的复合索引:
db.testindx.ensureIndex({"Age": 1, "Name": 1, "Class": 1})
此时我们的查询做排序能使用索引的如下:
> db.testindx.find().sort({"Age":1})
> db.testindx.find().sort({"Age":1,"Name":1})
> db.testindx.find().sort({"Age":1,"Name":1, "Class":1})
而下面的排序查询则不会使用到索引
> db.testindx.find().sort({"Gender":1, "Age":1, "Name": 1})
我们可以通过MongoDB提供的explain()函数查看执行计划来进行这方面的诊断。
以上是关于MongoDB中的索引的主要内容,如果未能解决你的问题,请参考以下文章