MongoDB索引

索引就是用来加速查询的。数据库 索引与书籍的索引类似:有了索引就不需要翻遍整本书,数据库可以直接在索引中查找,使得查找速度能提高几个数量级。

1.1索引简介

现在要依照某个键查找:

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

当查询中仅使用一个键时,可以对该键建立索引,以提高查询速度。

对"username"建立索引,创建索引要使用ensureIndex方法:

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

对于同一个集合,同样的索引只需要创建一次,反复创建是徒劳的。对某个键创建索引会加速对该键的查询。然而,对于其他查询可能没有帮助,即使是查询包含了被索引的键。

如下面的查询就不会从上面建立的索引中获得任何性能的提升。

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

实践证明,一定要创建查询中用到的所有键的索引,如上面的查询,应该建立日期和用户名索引:

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

传递给ensureIndex的文档其形式与传递给sort的文档形式一样:一组值为1或-1的键,表示索引创建的方向。若索引只有一个键,则方向无关紧要。若是有多个键,就得考虑索引的方向问题了。

比如以{"username" : 1,"age" : -1}这种方式创建索引,MongoDB会将用户名严格地按照字母升序排列,同名的组按照年龄降序排列。这对{"username" : 1,"age" : -1}这样的排序做了优化,但是对{"username" : 1,"age" : 1}就不那么有效了。要是想对其优化的话,则需建立{"username" : 1,"age" : 1}的索引以便按照年龄升序组织。

对用户名和年龄的索引同样能加快对用户名的查询。一般来说,如果索引包含N个键,则对于前几个键的查询都会有帮助。

只有使用索引前部的查询才能使用该索引。

MongoDB的查询优化器会重排查询项的顺序,以便利用索引:如查询{x:"foo",y:"bar"}的时候,已经有了{y:1,x:1}的索引,MongoDB会自己找到并利用它。

创建索引的缺点就是每次插入、更新和删除时都会产生额外的开销。这是因为数据库不但要执行这些操作,还要将这些操作在集合的索引中标记。因此,尽可能少创建索引,每个集合默认的最大索引个数为64个。

有些时候,最有效的办法居然是不使用索引,一般来说,要是查询要返回集合中一半以上的结果,用表的扫描会比几乎每条文档都检查索引要高效一些,所以,查询是否存在某个键,或者检查某个布尔类型值为真还是假,真的没有用索引的必要。

拓展索引

建立索引时要考虑的问题:

  1. 会做什么样的查询?其中哪些键需要索引?
  2. 每个键的索引方向是怎样的?
  3. 如何应对拓展?有没有种不同键的排列可以使常用数据更多地保留在内存中?

索引内嵌文档中的键

对内嵌文档的键建立索引和普通键创建的索引并没有什么区别。如想按日期搜索博客文章的评论,可以在由内嵌的"comments"文档组成的数组中对"date"键创建索引:

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

为排列创建索引

 随着集合的增长,需要针对查询中大量的排序做索引。如果没有索引的键调用sort,mongodb需要将所有数据提取到内存来排序。

按照排序来索引以便让MongoDB来按照顺序提取数据,这样就能排序大规模数据,而不必担心用光内存。

索引名称

集合中的每个索引都有 一个字符串类型的名字,来唯一标识索引,服务器通过这个名字来删除或者操作索引。默认情况下,索引名类似keyname1_dir1_keyname2_dir2_..._keynameN_dirN这种形式,其中keynameX代表索引的键,dirX代表索引的方向(1或者-1)。要是索引的键特别多,这样命名就略显愚笨了,不过可通过ensureIndex的选项来指定自定义的名字:

> db.foo.ensureIndex({a:1,b:1,c:1,......,z:1},{name : "alphabet"})

1.2 唯一索引

唯一索引可以确保集合的每一个文档的指定键都有唯一值。如果想保证文档的"username"键都有不一样的值,创建一个唯一索引就好了:

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

默认情况下,insert并不检查文档是否插入过了。所以,为了避免插入的文档中包含与唯一键重复的值,可能要用安全插入才能满足要求。这样在插入文档时会看到存在重复键错误提示。

"_id"唯一索引创建普通集合时一同创建。这个索引和普通唯一索引只有一点不同,就是不能删除。

消除重复

当为已有的集合创建索引,可能有些值已经有重复了。若是真的发生了这种情况,那么索引的创建就是失败。

dropDups选项就可以保留发现的第一个文档,而删除接下来的有重复值的文档:

> db.test.ensureIndex({name:1},{unique:true,"dropDups":true})

如果数据很重要的话,还是写个脚步做个预处理比较稳妥。

复合唯一索引

创建复合唯一索引的时候,单个键的值可以相同,只要所有键的值组合起来不同就好。

GirdFS是mongodb中存储大文件的标准方式,其中就用到了复合唯一索引。

1.3 使用explain和hint

explain是一个非常有用的工具,会帮助获得查询方面诸多有用的信息。只要对游标调用该方法,就可以得到查询细节。

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

explain会返回查询使用的索引情况(如果有的话),耗时及扫描文档数的统计信息。

如果发现MongoDB用了非预期的索引,可以用hint强制使用某个索引。如希望MongoDB在上一个例子中使用{"username":1,"age":1}索引,则需要:

> db.c.find({age : 14,username : /.*/}).hint({username : 1,age : 1})

多数情况下这种指定都没什么必要。MongoDB的查询优化器非常智能,会替你选择该用哪个索引。

1.4 索引管理

 索引的元信息存储在每个数据库的system.indexes集合中。这是一个保留集合,不能对其插入或者删除文档。操作只能通过ensureIndex或者dropIndexes进行。

修改索引

建立索引即耗时也需要消耗很多资源,使用{"background":true}选项可以使这个过程在后台完成。同时正常处理请求,要是不包括background这个选项,数据库会阻塞建立索引期间的所有请求。

为已有文档创建索引比先创建索引再插入所有文档要稍快一些。当然,要是集合的数据从无到有,事先创建一个索引也未尝不可。

用dropIndexes加上索引名删除索引,通常要查一下system.indexes集合来找出索引名,因为即便是自动生成的名字也会因为驱动程序不同而不同。

//删除索引
> db.test.dropIndex({"username":1})
//删除所有索引
> db.test.dropIndex({})

1.5 地理空间索引

地理空间索引同样可以由ensureIndex来创建,参数为"2d":

> db.map.ensureIndex({"gps": "2d"})

"gps"键的值必须是某种形式的一对值:一个包含两个元素的数组或是包含两个键的内嵌文档。
下面都是有效的:

{gps : [0,100]}
{gps : {x : -30,y : 30}}
{gps : {latitude : -180,longitude : 180}}

键名的话可以随意,如{gps : {foo : 0,bar : 1}}

地理空间查询以两种方式进行,即普通查询(find)或者数据库命令。find的方式与一般查询差别不大,只不过用了"$near"。需要两个目标值的数组作为参数:

//返回离点最近的10个文档
> db.map.find({gps : {$near : [40,-73]}}).limit(10)
//也可用geoNear来完成
> db.runCommand({geoNear:"map",near:[40,-73] , num : 10})

按照离点(40,-73)由近及远的方式将map集合的所有文档都返回。没有指定limit,默认返回100文档。

MongoDB不但能找到靠近一个点的文档,还能找到指定形状的文档。将原来的"$near"换成"$within"。"$within"获取数量不断增加的形状作为参数。

//矩形使用"$box"
> db.map.find({gps:{$within : {$box : [[10, 20], [15, 30]]}}})

"$box"参数是两个元素的数组,第一个元素指定了左下角的坐标,第二个指定右上角的坐标

//$center找到圆形内部的所有点,参数变成了圆心和半径
> db.map.find({gps : {"$within" : {$center : [[12 , 25], 5]}}})

复合地理空间索引

应用经常要查找的东西往往不是一个地点,如找出周围的咖啡店或披萨店,将地理空间索引和普通索引组合起来可以满足这种需求。如查询"location"和"desc"

> db.ensureIndex({location:"2d","desc":1})
//查询
> db.map.find({location : {"$near" : [-70, 30]}, "desc" : "coffeeshop"}).limit(1)

 

posted on 2016-03-20 16:52  gimin  阅读(264)  评论(0)    收藏  举报