索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文档并选取那些符合查询条件的记录。

mongo中的索引实现方式和引擎有关,版本3.0之前的的引擎默认是MMAPv1,使用内存映射机制,之后的版本引擎开始支持可插拔的引擎方式,并提供WiredTiger引擎,并且从3.2版本开始,WiredTiger成为了之后版本的默认引擎。mongoDB只需修改参数,就可以更换引擎,关于引擎的话题,我们会在别的博客中进行详细介绍。虽然引擎众多,但是大多数引擎索引的实现方式都是使用B-树实现的。

下面,我们对索引类型进行依次的介绍:

唯一索引

有时候我们要确保文档里的字段唯一,例如_id或者userId唯一。唯一索引会在所有文档中确保唯一性,如果使用已经存在的userId插入到集合中,会抛出异常。

db.xxx.createIndex({userId: 1}, {unique: true});

其中users为集合名(表名),userId为集合中的字段,其中1表示升序,-1表示降序排列,使用unique来指明创建的索引为唯一索引。

稀疏索引

索引默认事密集型的。这意味着对于索引集合中的每一个文档,存在一个对应的入口,即时这个文档的索引值缺失(null)。

但是存在两种情况,密集索引变得不适用。第一种当我们想要在集合里不是每个文档都出现的字段上建立唯一索引时。例如我们想在运单号上字段上创建一个唯一索引,但是由于某些原因,运单号在存储到集合中时无法获取到,如果有唯一索引而尝试插入两个运单号为为null的文档,则第一个会成功,后续的插入都会失败,因为运单号为null的索引入口已经被占用了。第二种是集合钟的大量文档不包含所有的键值时。某些字段因为某些原因导致大量的null,如果相对这些字段创建索引,就有一半的入口都是null,这样会比较低效。这两种情况稀疏索引比较适用。

db.xxx.createIndex({wayBillNumber: 1}, {unique: true, sparse: true});

在创建索引的时候添加上sparse属性就可以方便的创建稀疏索引。

多键索引

多键索引可以给数据为数组的字段创建索引,它允许在索引里使用多个入口来连接同一个文档。

{
productName: "zhenai",
tags:["health", "accident"]
}

如上数据所示,如果在tags上建立索引,每个文档中的tags数组钟的值都会出现在索引里,就以为着针对这些数组的任意值在索引上的查询都会定位到文档上。

多键索引的创建和普通索引一致。

db.xxx.createIndex({tags: 1});

哈希索引

哈希索引的入口首先通过哈希函数来确定的。因为索引的值时最初参数的哈希值,所以哈希索引有一些限制:

* 不支持范围查找;
* 不支持多键哈希;
* 浮点数在哈希之前转换为整数,因此,3.1和3.5有相同的哈希索引

虽然有这么多限制,哈希索引也有它特有的优势------哈希索引上的入口时均匀分布的。即有键值数据不均匀分布时,哈希函数可以创建均匀性。哈希索引对于分片集合非常有用,分片索引决定文档分配到哪个片中。

如果使用分片存储(sharding),哈希索引会非常有用。参考mongo默认的主键_id,因为其最主要的生成是基于创建时间生成的[参考附录1],在同一时刻创建的文档的索引入口会彼此接近。如果使用这些 _id来决定文档存储在哪个片(机器)中,那么这些文档可能会在同一个机器上,这可能会导致一些负载的问题。

db.xxx.createIndex({tags: "hashed"})

地址空间索引

地理空间索引可以帮助我们在包含地理空间形状和点集的集合上高效地执行空间查询。例如,用户想查询附近的可以订餐的餐馆,执行这个查询就需要索引可以高效的计算出地理位置的距离,包括地球的弧度。

db.xxx.createIndex({location: "2d"})
db.xxx.createIndex({location: "2dsphere"})

地理空间索引分为两类,2d位平面,2dsphere为球面。

复合索引

以上都是单键索引,接下来我们讲mongo里的复合索引。

mongo在2.6版本开始,可以在一个查询里使用多个索引,但是最好还是只使用一个索引。并且如果经常需要查询多个字段,且又希望改善性能,那我们可以使用复合索引。

db.product.find({
supplier: 'azjd',
price: {
$lt: 200
}
});

这个查询要找出供应商为azjd并且价格低于两百元的所有保险产品记录。如果使用单个的索引,需要单独遍历每个数据结构,找到他们的磁盘位置,计算交集,如下如所示:

MongoDB中索引介绍_后台索引

复合索引是每个入口有多个键值组成的单个索引,在supplier和price上构建复合索引,结果如下图所示:

MongoDB中索引介绍_索引_02

同样要满足上面的查询,只要要在索引中找到供应商为azjd并且价格低于200元的第一个入口,就可以使用连续的扫描找到查询结果。

db.product.createIndex({supplier: 1, price: 1})

mongo中的复合索引和mysql中的复合索引规则基本相同,索引键值的顺序非常重要。如果price上只有一个复合索引,如下查询操作是无法使用索引的,

db.product.find({
price: {
$lt: 200
}
})

在创建索引时,如果集合中的数据量已经很大了,为集合创建索引会花费很多的时间,而在这个时间内,数据库会被锁住,其他的客户端无法同时读/写此数据库了,如果在生产环境,这就会很糟糕。因此,mongo提供另一种创建索引的方式,即后台创建索引,后台索引创建时,mongo依然可以提供这个集合(表)的读写操作服务。采用后台创建服务来创建索引时只需指定background参数即可。

db.product.createIndex({supplier: 1, price: 1}, {background: true});

后台索引有些问题需要注意:


  1. 索引创建的时间会相对直接创建索引变长,
  2. 创建索引的这个连接会不可用,需等待创建工作完成才可用,
  3. 索引必须全部创建完成,对应集合才可以使用索引
  4. 索引创建期间不能完成涉及该集合的相关管理操作
  5. 因为意外中断(服务器重启)导致的创建失败,会在服务器恢复正常后继续创建
  6. 创建过程中,服务性能会略微有所下降
    基于上述的创建工作完成后,我们可以使用getIndexes方法来查看集合中已经创建的索引。

db.xxx.getIndexes();

MongoDB中的索引可以做很多事,地理空间索引,还有我们下边博客要将的文本搜索,都可以为我们提供更好的一个服务,使用好MongoDB中的索引,必定能为我们的服务提供更强大的数据服务。