索引就像图书馆的目录一样,可以让我们快速定位到需要的内容。关系型数据库中有索引,NoSQL中当然也有。

索引创建

默认情况下,集合中的 _id 字段就是索引,我们可以通过 getIndexes() 方法来查看一个集合中的索引:

db.index_1.getIndexes()

结果如下:

[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_" } ]

我们看到这里只有一个索引,就是_id。

现在我的集合中有10000个文档,我想要查询x为1的文档,我的查询操作如下:

db.index_1.find({x:1})

这种查询默认情况下会做全表扫描,我们可以用上篇介绍的explain()来查看一下执行计划,如下:

db.index_1.find({x:1}).explain("executionStats")

结果如下:

{
        "explainVersion" : "1",
        "queryPlanner" : {
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1,
                "executionTimeMillis" : 4,
                "totalKeysExamined" : 0,
                "totalDocsExamined" : 10000,
                "executionStages" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "x" : {
                                        "$eq" : 1
                                }
                        },
                        "nReturned" : 1,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 10002,
                        "advanced" : 1,
                        "needTime" : 10000,
                        "needYield" : 0,
                        "saveState" : 10,
                        "restoreState" : 10,
                        "isEOF" : 1,
                        "direction" : "forward",
                        "docsExamined" : 10000
                }
        },
        "ok" : 1
}

结果比较长,我摘取了关键的一部分。我们可以看到查询方式是全表扫描,一共扫描了10000个文档才查出我想要的结果。实际上我要的文档就排第二个,但是系统不知道这个集合中一共有多少个x为1的文档,所以会把全表扫描完,这种方式当然很低效。但是如果我加上limit,如下:

db.index_1.find({x:1}).limit(1)

此时再看查询计划发现只扫描了两个文档就有结果了。但是如果我要查询x为9999的记录,那还是得把全表扫描一遍。此时,我们就可以给该字段建立索引,索引建立方式如下:

db.index_1.createIndex({x:1})

1表示升序,-1表示降序。当我们给x字段建立索引之后,再根据x字段去查询,速度就非常快了。我们看下面这个查询操作的执行计划:

db.index_1.find({x:9999}).explain("executionStats")

结果如下:

{
        "explainVersion" : "1",
        "queryPlanner" : {},
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1,
                "executionTimeMillis" : 1,
                "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,
                        "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,
                                "keyPattern" : {
                                        "x" : 1
                                },
                                "indexName" : "x_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "x" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "x" : [
                                                "[9999.0, 9999.0]"
                                        ]
                                },
                                "keysExamined" : 1,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0
                        }
                }
        },
        "ok" : 1
}

此时调用getIndexes()方法,可以看到我们刚刚创建的索引,如下:

[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "x" : 1
                },
                "name" : "x_1"
        }
]

我们看到每个索引都有一个名字,默认的索引名字为字段名_排序值。当然我们也可以在创建索引时自定义索引名字,如下:

db.index_1.createIndex({x:1},{name:"myfirstindex"})

此时创建好的索引如下:

[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "x" : 1
                },
                "name" : "myfirstindex"
        }
]

当然索引在创建过程中还有许多其他可选参数,如下:

参数

描述

name

索引名称

background

创建索引过程会阻塞其它数据库操作,background可以指定以后台方式创建索引,默认为false

unique

建立的索引是否唯一,默认为false

sparse

对文档中不存在的字段是否不启用索引,默认false

v

索引的版本号

weights

索引的权重,表示该索引相对于其他索引字段的得分权重

查看索引

上文我们介绍了getIndexes()可以用来查看索引,我们还可以通过totalIndexSize()来查看索引的大小,如下:

db.index_1.totalIndexSize()

删除索引

我们可以按名称删除索引,如下:

db.index_1.dropIndex("myfirstindex")

表示删除一个名为myfirstindex的索引。当然我们也可以删除所有索引,如下:

db.index_1.dropIndexes()

该方法会删除所有非_id索引。

总结

索引是个好东西,可以有效提高查询速度,但是索引会降低插入、更新和删除的速度,因为这些操作不仅要更新文档,还要更新索引。MongoDB限制每个集合上最多有64个索引,我们在创建索引时要仔细斟酌索引的字段。