MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致,大部分优化MySQL索引的技巧也同样适用于mongodb。由于集合中的键(字段)可以是普通数据类型,也可以是子文档。MongoDB可以在各种类型的键上创建索引。索引是对数据库表中一列或多列的值进行排序的一种特殊的数据结构,存储在一个易于遍历读取的数据集合中。当查询中用到某些条件时,可以对该键建立索引,能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。然而,这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,性能非常低下,没法接受的。


索引有方向的,倒序还是升序。每个集合默认的最大索引个数为64个。


下面分别讲解各种类型的索引的创建,查询,以及索引的维护等。


初始化数据:


db.user.save({"name":"Jack","age":25});
db.user.save({"name":"Jack","age":10});
db.user.save({"name":"Lucy","age":22});
db.user.save({"name":"Lucy","age":33});
db.user.save({"name":"Lilei","age":30});
for(var i=0;i<10000;i++){db.user.save({"name":"Jack"+i,"age":(i+10)%100});}



一、创建索引


语法:


>db.COLLECTION_NAME.ensureIndex({KEY:1})


KEY 值为要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1即可。


ensureIndex() 也可以设置使用多个字段创建索引(在关系型数据库中称作复合索引)。


实例:


>db.user.ensureIndex({"name" : 1},{'background':true});


可接受的参数:


Parameter

Type

Description

background

Boolean

false。

unique

Boolean

false.

name

string

索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。

dropDups

Boolean

false.

sparse

Boolean

false.

expireAfterSeconds

integer

指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。

v

index version

索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。

weights

document

索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。

default_language

string

对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语

language_override

string

对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.

二、查看索引

语法:


> db.COLLECTION_NAME.getIndexes() 或者db.system.indexes.find()


查看实例:


> db.user.getIndexes();
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "ns" : "lmis.user",
                "name" : "_id_"
        },
        {
                "v" : 1,
                "key" : {
                        "name" : 1
                },
                "ns" : "lmis.user",
                "name" : "name_1",
                "background" : true
        }
]


查询索引大小:


语法:


> db.COLLECTION_NAME.totalIndexSize()


> db.user.totalIndexSize();
842128


三、删除索引


关系型数据库中,表被删除了,索引也不会存在。在MongoDB中不存在删除集合的说法,就算集合数据清空,索引都是还在的,要移除索引还需要手工删除。


语法:


db.COLLECTION_NAME.dropIndex(index)


db.COLLECTION_NAME.dropIndexes() //删除全部索引,_id索引不会被删除


>db.user.dropIndexes();
{
        "nIndexesWas" : 2,
        "msg" : "non-_id indexes dropped for collection",
        "ok" : 1
}


四、重建索引


重建索引会锁住集合的。用repair命令修复数据库时,会重建索引的。


语法:


>db.collection.reIndex()或db.runCommand({reIndex:'collection'})


>db.user.reIndex()
{
        "nIndexesWas" : 1,
        "msg" : "indexes dropped for collection",
        "nIndexes" : 1,
        "indexes" : [
                {
                        "key" : {
                                "_id" : 1
                        },
                        "ns" : "lmis.user",
                        "name" : "_id_"
                }
        ],
        "ok" : 1
}


五、索引分类


1.默认索引


MongoDB有个默认的"_id"的键,他相当于"主键"的角色。集合创建后系统会自动创建一个索引在"_id"键上,它是默认索引,索引名叫"_id_",是无法被删除的。


2.单列索引


在单个键上创建的索引就是单列索引,如我们创建的db.user.ensureIndex({"name" : 1}


3.组合索引


同时对多个键创建组合索引。如下:


>db.user.ensureIndex({"name" : 1,"age" : 1}
//以下的查询将使用到此索引
> db.user.find({"name":'Jack',"age":20})
> db.user.find({"name":'Jack'})
> db.user.find().sort({"name":1})


对多个值进行组合索引,查询时,子查询与索引前缀匹配时,才可以使用该组合索引。


4.嵌入式索引


为内嵌文档的键创建索引与普通的键创建索引并无差异。


如:


> db.user.save({"name":"Jack","age":25,"address":{"city":"beijing"}});
> db.user.ensureIndex({"address.city":1})


5.文档式索引


索引建立在嵌入式文档类型的字段上。


如:


> db.user.save({"name":"Jack","age":25,"address":{"city":"beijing"}});
> db.user.ensureIndex({"address":1})


6.唯一索引


只需要在ensureIndex命名中指定"unique:true"即可。唯一索引限制了对当前键添加值时,不能添加重复的信息。值得注意的是,当文档不存在指定键时,会被认为键值是“null”,所以“null”也会被认为是重复的,所以一般被作为唯一索引的键,最好都要有键值。可以使用dropDups来保留第一个文档,而后的重复文档将删除,这种方法慎重操作。


>db.user.ensureIndex({name:1},{unique:true});


7.sparse索引


对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档。默认值为 false。解决索引文件过大的问题。不会对该键值为空的行做索引。



>db.user.ensureIndex({name:1},{sparse:true}); 



8.过期索引-expireAfter



指定一个值,以秒为TTL控制多久MongoDB的文档保留在此集合。储存在过期时间的字段必须是时间类型( ISODate或 ISODate数组 ),不能使用复合索引。如果使用的是 ISODate数组,则按照最小时间进行删除。



> db.user.ensureIndex({name:1},{expireafterseconds:3600}); 


9.covered索引


如果你查找的值正好是在索引中,则可以直接返回索引中存的值,而不用到数据文件中查找。当用explain时,当indexOnly=true,表示有用到covered index。


10.全文索引


mongodb全文索引是在2.4版本引入的,下一节介绍。


理位置索引


关于LBS相关项目,一般存储每个地点的经纬度的坐标, 如果要查询附近的场所,则需要建立索引来提升查询效率。 Mongodb专门针对这种查询建立了地理空间索引。 2d和2dsphere索引,分别是针对平面和球面。


五、强制使用索引


如果发现用了非预期的索引,可以用hint来强制使用某个索引。使用hint命令,并不是所有操作走索引一定快。


如:> db.user.find({age:{$lt:20}}).hint({name:1,age:1})


使用explain命令查看执行计划,看看有没有走索引。


> db.user.find({age:{$lt:30}}).explain()
{
        "cursor" : "BasicCursor",
        "isMultiKey" : false,
        "n" : 3004,
        "nscannedObjects" : 10006,
        "nscanned" : 10006,
        "nscannedObjectsAllPlans" : 10006,
        "nscannedAllPlans" : 10006,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 9,
        "indexBounds" : {
   //没有走索引
        },
        "server" : "100.205:27017"
}


如上没有走索引。加上hint强制使用索引,如下:


> db.user.find({age:{$lt:30}}).hint({name:1,age:1}).explain()
{
        "cursor" : "BtreeCursor name_1_age_1",
        "isMultiKey" : false,
        "n" : 3004,
        "nscannedObjects" : 3004,
        "nscanned" : 9817,
        "nscannedObjectsAllPlans" : 3004,
        "nscannedAllPlans" : 9817,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 34,
        "indexBounds" : {
                "name" : [
                        [
                                {
                                        "$minElement" : 1
                                },
                                {
                                        "$maxElement" : 1
                                }
                        ]
                ],
                "age" : [
                        [
                                -1.7976931348623157e+308,
                                30
                        ]
                ]
        },
        "server" : "100.205:27017"
}


如上使用了索引耗时(millis)反而变大。大多数情况下没必要指定,mongodb会选择用哪个索引,初次查询时,查询优化器会同时尝试各种查询方案,最先完成的被确定使用,其他的则终止掉。查询方案也会记录下来,查询优化器也会定期重试其他的方案,以防新的数据增加或索引改变,之前的方案不再是最优的。