Index 能够提高查询的性能,如果没有Index,MongoDB必须扫描整个collection,从collection的第一个doc开始,直到最后一个doc,即使第一个doc之后的所有doc都不满足查询条件。如果在Collection上有合适的Index,例如,unique index,那么MongoDB在按照index key查找到一个doc之后,就不会继续对其他doc查询,极大地提高查询性能。
MongoDB的 Index 结构跟关系型DB的NonClustered Index相似,都是BTree结构,在每个leaf node中,除了index key之外,还存储相应doc在disk上的地址。在MongoDB中,没有clustered index,因此,Collection初始的物理存储跟doc插入的顺序有关,MongoDB按照doc插入的顺序,依次将doc存储在disk上,插入顺序上相邻的doc在disk的物理位置上也是相邻的;对doc的修改可能对 collection 的物理存储发生变化,如果doc的修改不会导致doc的size增加,那么doc会继续存储在原来的存储空间中,而不会对collection的物理存储有影响,一旦修改操作导致doc的size增加,导致doc发生移动,那么collection的物理存储就会发生变化。
一,doc的移动影响collection的物理存储
如果数据修改增加了doc的size,使其不能继续存放在原来的存储空间中,那么MongoDB必须将其移动到collection的末尾,原先的存储空间被闲置,导致doc的存储密度下降,会严重影响查询性能。doc的移动过程是非常慢的,相当于在一个原子操作中,先做doc的 delete 操作,后做doc的 insert 操作。
doc移动的过程如下图所示:
对doc B进行修改,使其Size增大,原先的位置不能容纳B,MongoDB将B移动在Collection的末尾。原来的存储空间被闲置。
二,创建index
MongoDB的index是BTree 结构,BTee结构的特点是:查询每个值所要进行查询的次数时固定的,最小的值存储在最左边的叶子节点上,做大的值存储在最右边的叶子节点上,如图:
MongoDB默认按照“_id”字段的升序创建index,最后创建的doc位于index的右侧。如果每次查询时,都是查询最后的N个doc,那么按照"_id"的值倒叙查询,limit 前100,查询性能是十分快速的。也可以手动创建符合业务需要的Index,MongoDB使用 db.collection.createIndex(keys,option)函数创建index。
keys的格式是:{field:1/-1,,},field是doc的字段,1/-1 表示按照field排序的方向创建index:1表示按照field的升序创建,-1表示按照field的降序创建。
db.collection.createIndex(keys,option)
1,创建示例数据,按照 age 字段升序创建index
创建的Index按照age的升序存储age字段,在叶子节点中,除了age字段,叶子节点还会存储doc的地址(指针),用于定位相应的doc,查询除index key(age)之外的其他字段。下面的语句创建的index name是 age_1。
for(i=0;i<10000;i++)
{
db.foo.insert({"idx":i,name:"user "+1,age:i%90})
} --create index by age ascendant
db.foo.createIndex({age:1})
2,查看查询的query plan
在示例中,由于查询语句中没有设置projection,MongoDB返回doc中的所有fields,由于index:age_1只包含age字段,其他字段必须定位到原doc中获取,因此多一次寻址操作。
db.foo.find({age:22}).explain("executionStats")
在查询语句中设置Projection,只返回age字段,那么index:age_1 就能包含结果集中所有字段,不需要定位到原doc中,不仅提高查询性能,而且减少Disk IO 和内存的使用量,因此,应对每个结果集设置Projection,不要返回"_id"字段等其他不需要的字段。通过搜索index就能获取所有field的index是覆盖索引(convered index),覆盖索引不需要定位到原doc中。
db.foo.find({age:22},{age:1,_id:0}).explain("executionStats")
三,index 和 排序
排序是一个非常耗费内存资源的操作,在MongoDB中,如果排序的中间结果集大小消耗的系统内存超过32MB,MongoDB就会报错,拒绝如此多的数据进行排序。32MB是一个阈值,如果超出值,那么必须使用 index 来获取经过排序的数据集。
MongoDB:When unable to obtain the sort order from an index, MongoDB will sort the results in memory, which requires that the result set being sorted is less than 32 megabytes. When the sort operation consumes more than 32 megabytes, MongoDB returns an error.
通过Index来执行排序操作,要求排序的字段和index key的前缀字段相同,如果满足该条件,那么MongoDB会直接返回顺序的结果集,而不需要执行实际的排序操作。例如,如果index key是{age:1,name:1},如果排序操作是sort({age:1}),或 sort({age:1,name:1}),符合index前缀排序,那么结果集会直接返回,不需要排序;如果排序操作是sort({name:1}),或sort({name:1,age:1}),不符合index前缀排序,结果集还是需要在内存中进行排序,如果内存消耗超过32MB,MongoDB报错。
示例,符合前缀排序的Index 和 sort 操作
db.foo.find({age:22}).sort({age:1})
由于MongoDB通过Index来执行排序操作,并且MongoDB对排序操作消耗的内存资源有严格限制,因此,在创建index,应在查找和排序之间做折衷,在满足排序操作的前提下,使查询性能更高。在创建index时,使用 {"Sort Key":1, "query filter":1} 格式是非常有用。
四,对内嵌doc进行索引
MongoDB index的强大之处在于能够在内嵌doc的字段上创建index,创建的语法和普通doc一致,在引用内嵌doc中的字段时,使用dot notation,可以对任意深度的内嵌doc字段建立index。
例如,doc结构如下,contact是内嵌doc,按照 contact.phone 字段升序创建索引。
{
name:"u1",
age:22,
contact:
{
phone:123
email:"xx@163.com"
}
} --create index
db.foo.createIndex({"contact.phone":1})
五,Index 维护
1,查看在collection上创建的index
使用db.collectionName.getIndexes() 查看给定collection上的所有index信息:
- key是指index key的定义,包括两部分:key 和排序的方向;
- name是index name;
- ns是namespace;
- v 标识index 版本,如果索引中包含"v":1,说明index是以新格式存储的,性能较高。
db.collection.getIndexes()
2,删除index
db.collection.dropIndex(index)
方式一,按照index name 删除index
db.foo.dropIndex("age_1")
方式二,按照Index key删除index
db.foo.dropIndex({age:1})
3,重建collection中的所有index
db.collection.reIndex()
The db.collection.reIndex() drops all indexes on a collection and recreates them. This operation may be expensive for collections that have a large amount of data and/or a large number of indexes.
参考doc: