Mongodb索引探索

索引是数据库中的一个重要对象,主要用于支持高效查询操作。如果没有索引,数据库就只能进行全表扫描,效率将极为低下。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则会使查询操作直接忽略这些记录。

好了,概念说了很多,来讲一下索引的具体操作吧。创建一个索引很简单,看看下面这些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// 单键索引
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上面的集合信息

1
2
3
4
5
// 单个Collection
db.Student.getIndexes();

// DB中所有的Index
db.system.indexes.find();

删除索引

1
2
3
4
5
// 删除在某个field上面的索引
db.Student.dropIndex({name:1});

// 根据索引名删除
db.system.indexes.remove({name:"code_-1"})

如何修改索引呢?没有特定修改命令,一般是先删除,然后创建新的索引。

系统运行一段时间以后,随着数据的累加,业务需求的变化,可能会需要对索引进行重建(rebuild),则可以做这个操作:

1
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子句可以使用多个),所以看看下面的情况会是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//存在两个索引:
{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}降序,但是对于单键索引,排序的时候升序降序都会命中。如:

1
2
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} 是无法命中的。

如果排序也希望命中索引的话,这里分为两种情况:

  • 排序字段以索引开始键开头

    1
    2
    // 因为查询条件中不存在索引开始键(a:1),要想命中索引,排序必须以索引开始键开头
    db.mycoll.find({b:{$gt:1}}).sort({a:1,b:1,c:1});
  • 排序字段不以索引开始键开头

    1
    db.mycoll.find({a:{$gt:1}}).sort({b:1,c:1});

当然,排序里面还有更为细致的问题,就是查询条件如果有索引field的精准匹配(equal),则排序也能更简单:

1
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.

代码

1
2

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等较为复杂的索引类型,这个在以后的文章中再来分析。