索引是数据库中的一个重要对象,主要用于支持高效查询操作。如果没有索引,数据库就只能进行全表扫描,效率将极为低下。mongodb的索引体系比较庞大,按照索引类型,我准备分这么几个部分来进行阐述:
基本索引
Text索引
GEO索引
概述
本文将简单介绍常用的基本索引类型,已经索引的相关操作。根据官方的文档,Mongodb有这么几种常见索引:
Default _id 主键索引,默认作用在
_id
上Single Field 单键索引,针对单个field的索引
Compound 复合索引,针对多个field的索引
Multikey Index,这个我都不知道怎么翻,多键索引吧,其实就是针对数组子项的索引,因为数组有多个元素,每个元素都可能的key,如果有一个索引A作用在这个key上,这就是所谓的Multikey index
Geospatial Index 针对地理位置信息的索引
Text Index 支持全文搜索的索引,2.4才支持
Hashed Index , To support hash based sharding, MongoDB provides a hashed index (page 22) type, which indexes the hash of the value of a field.
同时,mongodb提供了两个索引的属性:
Unique 唯一性,保证索引作用的field上的value是唯一的。
Sparse 稀疏性,如果一个Collection中的某个field A 只存在于某些Document上,而 A 上同时建立了索引,那么用Sparse则会使查询操作直接忽略这些记录。
好了,概念说了很多,来讲一下索引的具体操作吧。创建一个索引很简单,看看下面这些代码:
// 单键索引
db.Student.ensureIndex({code:1});
// 复合索引
db.Student.ensureIndex({name:1,time:-1})
// Multikey Index
db.Student.ensureIndex({faver.id:1});
// 唯一索引
db.Student.ensureIndex({code:1},{unique:1});
// 唯一索引同时删除重复值
db.Student.ensureIndex({code:1},{unique:1,dropDups:1});
// 唯一稀疏索引
db.Student.ensureIndex({code:1},{unique:1,sparse:1});
获取一个Collection上面的集合信息
// 单个Collection
db.Student.getIndexes();
// DB中所有的Index
db.system.indexes.find();
删除索引
// 删除在某个field上面的索引
db.Student.dropIndex({name:1});
// 根据索引名删除
db.system.indexes.remove({name:"code_-1"})
如何修改索引呢?没有特定修改命令,一般是先删除,然后创建新的索引。
系统运行一段时间以后,随着数据的累加,业务需求的变化,可能会需要对索引进行重建(rebuild),则可以做这个操作:
db.collection.reIndex()
rebuild会先删除集合上的所有索引,包括_id索引,然后重建。这种操作往往和耗时,最好在系统资源充足的时候做。
细节
1. 限制
mongodb对索引的使用和管理也有一些限制
索引key的总容量不能大于1024byte,否则以后的索引将创建不了
单个集合不能超过64个索引
单个索引的名字长度(包括命名空间)不能超过125 个字符
复合索引最多只能作用在31个field上
一个查询不能同时使用text and Geospatial 索引
上面列的只是一些大的限制,在具体场景中还有很多索引相互冲突,或者使用不当造成索引无法命中的情况,所以还要看看更细节的一些东西。
2. 使用策略
2.1 _id 主键索引
这个是系统自动创建的,不能删除,除非你Drop掉整个Collection。这个效率是非常高的,对于一些数据量很大,但是没有排序需求的集合(如日志表),在分页策略上应该使用_id来进行分页。
2.2 single 单键索引
mongodb 不限制你在任何field上面创建单键索引,但是一个查询一次只能使用一个索引($or子句可以使用多个),所以看看下面的情况会是这样的:
//存在两个索引:
{code:1},
{name:1}
//这里mongodb只会命中一个索引,具体是哪个由查询分析器决定
db.Student.find({code:{$lt:10},name:{$regex:/^a/}}).explain();
{
"cursor" : "BtreeCursor name_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 4,
"nscannedAllPlans" : 4,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 46,
"indexBounds" : {
"name" : [
[
"a",
"b"
]
]
},
"server" : "pormatoMacBook-Pro.local:27017"
}
索引还存在排序问题,{a:1}升序 / {a:-1}降序
,但是对于单键索引,排序的时候升序降序都会命中。如:
db.Student.find().sort({name:1});
db.Student.find().sort({name:-1});
2.3 复合索引
多数情况下,应该考虑复合索引而非单键索引,因为复合索引会包含部分单键索引。例如:
对于索引:
{a:1,b:1,c:1}
相当于该集合拥有了:
{a:1}
, {a:1,b:1}
, {a:1,b:1,c:1}
但是:
{b:1}
,{c:1}
, {b:1,c:1}
是无法命中的。
如果排序也希望命中索引的话,这里分为两种情况:
- 排序字段以索引开始键开头
// 因为查询条件中不存在索引开始键(a:1),要想命中索引,排序必须以索引开始键开头
db.mycoll.find({b:{$gt:1}}).sort({a:1,b:1,c:1});
- 排序字段不以索引开始键开头
db.mycoll.find({a:{$gt:1}}).sort({b:1,c:1});
当然,排序里面还有更为细致的问题,就是查询条件如果有索引field的精准匹配(equal),则排序也能更简单:
db.mycoll.find({a:1}).sort({b:1});
同样,复合索引也存在索引反序问题,这里和单键索引一样,只有完全反序才能命中:
对于索引{a:1,b:-1}
, {a:-1,b:1}
是可以命中的,反过来也成立。但是:{a:1,b:1}
或者{a:-1,b:-1}
是无法命中索引的。
3 MultiKey Index 多键索引
多键索引是作用在 array field上的element中的某个field上的索引。这个没有太多的特别之处,唯一要注意的是,如果一个索引是复合多键索引
,那么这个索引的field中只能有一个array类型。例如:
{a:1,b:[{b1:1,b2:1}]
这个是Ok的, {a:[a1:1,a2:1],b:[{b1:1,b2:1}]
这种索引则是非法的。
其它
mongdb中还存在一种Cover Index的说法。它发生在如下的情况中:
all the fields in the query are part of an index,
and
all the fields returned in the results are in the same index.
代码
db.mycoll.find({a:{$lt:100}},{a:1,_id:0}});
这个时候查询条件的field和 查询域field 完全一样,并且这个field刚好能命中索引的话,这个查询效率将非常的高,因为mongodb不会再去硬盘进行扫描,而是直接将Index信息返回。
这里需要知道的是如果在下面两种情况下,Cover Index将无法生效
any of the indexed fields in any of the documents in the collection includes an array. If an indexed field is an array, the index becomes a multi-key index , index and cannot support a covered query.
数组field将直接会使索引变为多键索引any of the indexed fields are fields in subdocuments. To index fields in subdocuments, use dot notation.
查询域总是会返回整个子文档的root节点
基本的索引类型就是这么多了,接下来还有Text , Geo ,hash等较为复杂的索引类型,这个在以后的文章中再来分析。