MongoDB010 索引

 

索引

1.      简介

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

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

对某个键创建的索引会加速对该键的查询。然而,对于其他查询可能没有帮助,即便是查询包含了被索引的键。例如,下面的查询就不会从先前建立索引中获得任何的性能提升

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

服务器必须”查找整本书”找到想要的日期。这个过程称作表扫描,就是在没有索引的书中找内容,要从第一页开始,从前翻到后。通常来说,要尽量避免让服务器做表扫描,因为当集合很大时会非常慢。

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

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

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

>db.ensureIndex({“username” : 1,”age” :-1})

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

对用户名和年龄的索引同样能加快对用户名的查询。一般来说,如果索引包含N个键,则对于前几个键的查询都会有帮助。比如有个索引{“a”:1,”b”:1,”c”:1,…,”z”:1},实际上是有了{“a”:1}、{“a”:1,”b”:1}、{“a”:1,”b”:1,”c”:1}等的索引。但是使用{“b”:1}、{“a”:1,”c”:1}等索引的查询则不会被优化,只有使用索引前部的查询才能使用该索引。

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

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

一定不要索引每一个键。这会导致插入非常慢,还会占用很多空间,并且很可能对查询速度提升不大。仔细考虑到底要做什么样的查询,什么样的索引适合这样的查询,通过explain和hint工具确保服务器使用了业已建立的索引。

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

 

1.1  扩展索引

假设我们有个集合,保存了用户的状态消息。现在想要查询用户和日期,取出某一用户最近的状态。以我们目前所学,我们会像下面这样创建一个索引:

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

这会使对用户和日期的查询非常快,但是并不是最好的方式。

因为用用户量不断变大时,每人每天有数十条状态更新。若是每条用户状态的索引值占用类似一页纸的磁盘空间,那么对于每次”最新状态”的查询,数据库都会将不同的页载入内存。若是站点太热门,内存放不下所有的索引,就会非常非常慢。

要是改变索引的顺序,变成{date:-1,user:1},则数据库可以将最后几天的索引保存在内存中,可以有效减少内存交换,这样查询任何用户的最新状态都会快很多。

所以,建立索引时要考虑如下问题。

(1)    会做什么样的查询?其中哪些键需要索引?

(2)    每个键的索引方向是怎么样的?

(3)    如何应对扩展?有没有不同的键的排列可以使常用数据更多地保留在内存中?

要是能回答这些问题,说明你已经做好了索引的准备了。

1.2  索引内嵌文档中的键

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

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

对内嵌文档的键索引与普通键索引并无差异,两者也可以联合组成复合索引。

1.3  为排序创建索引

随着集合的增长,需要针对查询中大量的排序做索引。如果对没有索引的键调用sort,MongoDB需要将所有数据提取到内存来排序。因此,可以做无索引排序是有个上限的,那就是不可能在内存里面做T级别数据的排序。一旦集合大到不能在内存中排序,MongoDB就会报错。

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

1.4  索引名称

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

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

索引名有字符个数的限制,所以特别复杂的索引在创建时一定要使用自定义的名字。可以用getLastError来检查索引是否成功创建了或者未成功创建的原因。

 

2.      唯一索引

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

>db.people.ensuerIndex({“username” : 1},{“unique” : true})

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

可能我们最熟悉的唯一索引就是对”_id”的索引了,这个索引是在创建普通集合时一同创建的。这个索引和普通唯一索引只有一点不同,就是不能删除。

如果没有对应的键,索引会引其作为null存储。所以,如果对某个键建立了唯一索引,但插入了多个缺少该索引键的文档,则由于文档包含null值而导致插入失败。

2.1  消除重复

当为已有的集合创建索引,可能有些值已经有重复了。若是真的发生这种情况,那么索引的创建就是失败。有些时候,可能希望将所有包含重复值的文档都删掉,dropDups选项就可以保留发现的第一个文档,而删除接下来的有重复值的文档:

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

这种做法多少有点鲁莽,如果数据很重要的话,还是写个脚本做个预处理比较稳妥。

 

2.2  复合唯一索引

 

3.      使用explain和hint

explain是一个非常有用的工具,会帮助你获得查询方面诸多有用的信息。只要对游标调用该方法,就可以得到查询细节。explain会返回一个文档,而不是游标本身,这是与多数游标方法不同之处。

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

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

例如,索引{“username” : 1}对单个键的查询非常有帮助,但是多数查询要复杂得多。比如,要做如下查询并排序:

 >db.people.find({“age”: 18}).sort({“username” : 1})

这时就搞不太准数据库到底用没用已经创建的索引,或者到底效率如何,使用explain就会得到当前查询所使用的索引,消耗了多少时间,以及数据库需要扫描多少文档才能得到结果。

查询索引信息可以使用如下指令:

//查询test库c集合,名为age_1的索引信息

>db.system.indexes.find({“ns” : “test.c”,”name” : “age_1”})

如果发现MongoDB用了非预期的索引,可以用hint强制使用某个索引。例如,希望MongoDB在查询时使用{“username”:1,”age”:1}索引,则需要

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

多数情况下这种指定都没什么必要。MongoDB的查询优化器非常智能,会替你选择该用哪个索引。初次做某个查询时,查询优化器会同时尝试各种查询方案。最先完成的被确定使用,其他的则终止掉。查询方案被记录下来,以备日后应对相同键的查询。查询优化器定期重试其他方案,以防因为添加新数据后,之前的方案不再是最优的了。只要关心给查询优化器建立可以选择的索引就可以了。


4 索引管理

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

       System.indexes集合包含每个索引的详细信息,同时system.namespaces集合也含有索引的名字。如果查看这个集合,会发现每个集合至少有两个文档与之对应,一个对应集合本身,一个对应集合包含的索引。

       修改索引:随着你的应用和你一起慢慢变老,你会发现数据或者查询已经发生了改变,原来的索引也不那么好用了。不过使用ensureIndex随时可以向现有集合添加新的索引。

       >db.people.ensureIndex({“username”:1},{“background”:true})

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

       阻塞的做法意味着让索引建立得更快,同时也意味着应用在此期间不能应答。即便在后中进行也会对正常操作有些影响,所以最好选在无关紧要的时刻。后台创建索引会加些负载,好在不会让服务器停机。

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

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

       >db.runCommand({“dropIndexes”: “foo”,”index” : “alphabet”})

       要删除所有索引,可以将index的值赋为*:

       >db.runCommand({“dropIndexes”:”foo”,”index”:”*”})

       另外一种删除索引的方式不是删除集合。这也会删除_id索引(还有集合的所有文档)。删除集合的所有文档(用remove的方式)并不影响索引,当有新文档插入时还会再生的。

 

5. 地理空间索引

       还有一种查询变得越来越流行:找到离当前位置最近的N个场所。MongoDB为坐标平面查询提供了专门的索引,称作地理空间索引。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值