读书人

MongoDB权威指南(四)- 索引

发布时间: 2012-08-17 02:08:34 作者: rapoo

MongoDB权威指南(4)- 索引

Note:mongoDB的索引的工作方式和关系数据库中的索引几乎是一样的。

1.索引简介

假设我们要按单个key查询,如下:

>?db.people.find({"username"?:?"mark"})

对单个的key进行查询的时候,我们可以在这个key上建立索引来提高查询速度。使用ensureIndex方法建立索引如下:

>?db.people.ensureIndex({"username"?:?1})

一个索引只需创建一次,重复创建相同的索引没有任何效果。

一个key上建立的索引会使对这个key的查询速度提高,除此之外就没有效果了,即使是查询包含这个key,如:

>?db.people.find({"date"?: date1}).sort({"date"?:?1,?"username"?:?1})

这个查询里,服务器必须遍历整个collction来找到日期符合的记录,这个过程叫做table scan(全表扫描),一般情况下你都会尽量避免

table scan,因为它对大型的collection运行非常缓慢。作为一条经验规则,你需要给它创建一个索引,包含了查询中用到的所有key的一个索引。

>?db.ensureIndex({"date"?:?1,?"username"?:?1})

传递给ensureIndex方法的document参数和sort方法的参数是一样的,它是一组key/value对,值可能是1或-1,代表索引进行的方向。

如果索引里只有一个key,方向就无所谓了,如果索引里有多个key,那么你就得考虑索引的方向问题。假设我们有下边的一些用户:

{?"_id"?: ...,?"username"?:?"smith",?"age"?:?48,?"user_id"?:?0?}
{?"_id"?: ...,?"username"?:?"smith",?"age"?:?30,?"user_id"?:?1?}
{?"_id"?: ...,?"username"?:?"john",?"age"?:?36,?"user_id"?:?2?}
{?"_id"?: ...,?"username"?:?"john",?"age"?:?18,?"user_id"?:?3?}
{?"_id"?: ...,?"username"?:?"joe",?"age"?:?36,?"user_id"?:?4?}
{?"_id"?: ...,?"username"?:?"john",?"age"?:?7,?"user_id"?:?5?}
{?"_id"?: ...,?"username"?:?"simon",?"age"?:?3,?"user_id"?:?6?}
{?"_id"?: ...,?"username"?:?"joe",?"age"?:?27,?"user_id"?:?7?}
{?"_id"?: ...,?"username"?:?"jacob",?"age"?:?17,?"user_id"?:?8?}
{?"_id"?: ...,?"username"?:?"sally",?"age"?:?52,?"user_id"?:?9?}
{?"_id"?: ...,?"username"?:?"simon",?"age"?:?59,?"user_id"?:?10?}

如果我们建立索引{"username" : 1, "age" : -1},mongoDB就会按下边的样子组织用户:

{?"_id"?: ...,?"username"?:?"jacob",?"age"?:?17,?"user_id"?:?8?}
{?"_id"?: ...,?"username"?:?"joe",?"age"?:?36,?"user_id"?:?4?}
{?"_id"?: ...,?"username"?:?"joe",?"age"?:?27,?"user_id"?:?7?}
{?"_id"?: ...,?"username"?:?"john",?"age"?:?36,?"user_id"?:?2?}
{?"_id"?: ...,?"username"?:?"john",?"age"?:?18,?"user_id"?:?3?}
{?"_id"?: ...,?"username"?:?"john",?"age"?:?7,?"user_id"?:?5?}
{?"_id"?: ...,?"username"?:?"sally",?"age"?:?52,?"user_id"?:?9?}
{?"_id"?: ...,?"username"?:?"simon",?"age"?:?59,?"user_id"?:?10?}
{?"_id"?: ...,?"username"?:?"simon",?"age"?:?3,?"user_id"?:?6?}
{?"_id"?: ...,?"username"?:?"smith",?"age"?:?48,?"user_id"?:?0?}
{?"_id"?: ...,?"username"?:?"smith",?"age"?:?30,?"user_id"?:?1?}

首先按名字的升序排列,名字相同的组里按降序排列。这索引会优化按{"username" : 1, "age" :-1}的排序操作,而对{"username" : 1, "age" : 1}

的排序效果就没那么好了,如果我们想优化{"username" : 1, "age" : 1},那就应该按{"username" : 1, "age" : 1}来建立索引,让年龄也升序排列。

对username和age建立的索引同时也会是对username的查询速度提高,通常,如果索引有N个key组成,对其中前边部分的查询速度也会提高。

例如,我们建立了索引{"a" : 1, "b" : 1, "c" : 1, ..., "z" : 1},那么效果上相当于我们也有了{"a" : 1}, {"a" : 1, "b" : 1}, {"a" : 1, "b" : 1, "c" :1}等等。

mongoDB的查询优化器会调整查询条件之间的顺序以利用索引,比如说你要查询{"x" : "foo", "y" : "bar"},而你的索引是{"y" : 1, "x" :1},优化器会自行调整。

索引的不利之处是给插入、更新、删除操作增添了一些负担。

在某些情况下,使用索引也许还不如不用索引。通常,如果查询返回collection里一半甚至更多的记录,那么相比为几乎每个document查找索引及其值,直接使用

全表扫描还更快些。

索引度量? (Scaling Index)

假设我们有个collection存储用户的状态消息,我们想按用户查询每个用户的最新状态,根据我们学到的知识,我们可能会这样建立索引:

>?db.status.ensureIndex({user :?1, date :?-1})

这个索引会使对user和date的查询速度提高,但实际上并不是最好的选择。按照这个索引,我们的数据可能是下边这个样子:

User?123?on March?13,?2010
User?123?on March?12,?2010
User?123?on March?11,?2010
User?123?on March?5,?2010
User?123?on March?4,?2010
User?124?on March?12,?2010
User?124?on March?11,?2010
...

如果只是这个数据规模,这样子看起来还是不错的,如果程序里有百万千万的用户,每个用户每天都会产生几十条状态更新呢?

如果每个用户的状态消息的索引记录都占用了磁盘空间一页的大小,那么每次进行最新状态查询时,数据都不得不加载另外一个页面进内存。

要是我们使用{date : -1, user : 1}做索引,那么数据库就可以将最近几天的索引保持在内存里,会有更少的页面对换,查询最新状态

也会更快。

对嵌入document的key建立索引

>?db.blog.ensureIndex({"comments.date"?:?1})

对嵌入的document建立索引和对顶级document建立索引没有差别,两者在组合索引里也可以组合使用。

为排序建立索引

如果对一个未建立索引的key调用sort方法,mongoDB需要取出所有的数据,放入内存然后排序,所以这个大小是有限制的,

如果collection太大,mongoDB就会返回一个错误。建立索引可以避免这个问题,使你可以对任意数量的数据进行排序而不会耗尽内存。

2.唯一索引?

唯一索引保证对于指定的key,collection里每个document中其值都是唯一的。如,要保证用户名都不重复:

>?db.people.ensureIndex({"username"?:?1}, {"unique"?:?true})

Note:如果key不存在,索引就会将其值存储为null,如果要再插入一个不含此key的document,插入就会失败,因为已经有了一个

值为null的document。

删除重复

对已有的collection建立唯一索引时,里边也许已经有了重复的值,这会导致索引建立失败,如果你想删掉具有重复值的document,

可以使用dropDups选项,遇到的第一个document被保留,其他的都被删除掉了。

>?db.people.ensureIndex({"username"?:?1}, {"unique"?:?true,?"dropDups"?:?true})

组合唯一索引

组合唯一索引里的单个key的值可以是重复的,但是所有key的组合必须是唯一的。

3.使用explain和hint

>?db.foo.find().explain()

explain方法返回一个document而不是游标本身,这个document包含了用到的索引、统计信息等。

举个例子,对一个无索引的collection执行一个最简单的查询({}),返回64个document,那么explain的输出为

>?db.people.find().explain()
{
  "cursor"?:?"BasicCursor",
  "indexBounds"?: [ ],
  "nscanned"?:?64,
  "nscannedObjects"?:?64,
  "n"?:?64,
  "millis"?:?0,
  "allPlans"?: [
  {
    "cursor"?:?"BasicCursor",
    "indexBounds"?: [ ]
  }
  ]

}
读书人网 >其他数据库

热点推荐