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中的索引的主要内容,如果未能解决你的问题,请参考以下文章

初识MongoDB中的索引

MongoDB中的索引列表?

MongoDB 索引

MongoDB中的索引

MongoDB 索引

mongoDB 3.2中的唯一索引忽略空值