一日三省吾身

博客园 首页 联系 订阅 管理

索引说明

  为集合选择合适的索引是提高性能的关键,数据库索引与书籍索引类似,数据库可以直接在索引中查找,在索引中找到条目后,就可以直接跳转到目标文档的位置,使查找速度提高几个数量级。由于机器性能和集合大小的不同,创建索引有可能需要花几分钟时间。如果对ensureIndex的调用没能在几秒钟后返回,可以在另一个shell中执行db.currentOp()或者检查mongod的日志来查看索引创建的进度。如果在一个集合上做查询,可以使用explain()函数查看MongoDB在执行查询的过程中所做的事。使用索引是有代价的:对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间,让数据发生变动时,mongodb不仅要更新文档,还要更新集合上的所有索引。因此,mongodb限制每个集合上最多只能创建64个索引,通常,再一个特定的集合上,不应该拥有两个以上的索引,于是,挑选合适的字段创建索引非常重要。创建索引的时候注意1是正序,-1是倒序;索引的创建在提高查询性能的同时会影响插入性能,对于经常查询少插入的文档可以考虑用索引;复合索引要注意索引的先后顺序;在做排序工作的时候如果是超大数据量也可以考虑加上索引来提高排序性能。

1>、复合索引(compound index):复合索引就是一个建立在多个字段上的索引。索引的值是按一定顺序排列的,因此,使用索引键对文档进行排序非常快,然而,只有在首先使用索引键进行排序时,索引才会有用。如果查询中有多个排序方向或者查询条件有多个键,这个索引就会非常有用。例如:db.users.find().sort({age:1,username:1})。建立的每一个索引条目都包含复合索引中的项,并且指向文档在磁盘上的存储位置,mongodb可以在任意方向上对索引进行遍历。mongodb对索引的使用方式取决于查询的类型:·点查询:用于查找单个值;·多值查询:查找多个值相匹配的文档。可以通过hint来强制mongodb使用某个特定的索引。例如:db.users.find({age:{$gte:21,$lte:30}}).sort({username:1}).hint({username:1,age:1}).explain()。在实际应用中,{sortKey:1,queryCriteria:1}索引通常是很有用的,这种方式也非常容易扩展。索引本质上是树,最小的值在最左边的叶子上,最大的值在最右边的叶子上。索引使用的方向,与排序方向相同就可以啦,注意,相互反转(每个方向都乘以-1)的索引是等价的:{age:1,username:1}适用的查询与{age:-1,username:1}是完全一样的。只有基于多个查询条件进行排序时,索引方向才是比较重要的,如果只是基于单一键进行排序,mongodb可以简单地从相反方向读取索引。例如:如果有一个基于{age:1}的排序和一个基于{age:-1}的索引,mongodb会在使用索引时进行优化,就如同存在一个{age:-1}的索引一样(所以不要创建两个这样的索引!)。只有在基于多建排序时,方向才变得重要。覆盖索引的使用:当一个索引包含用户请求的所有字段,可以认为这个索引覆盖了本次查询。在实际中,应该优先使用覆盖索引,而不是去获取实际文档,这样可以保证工作集比较小。复合索引具有双重功能,如果有一个拥有N个键的索引,那么你同时"免费"得到了所有这N个键的前缀组成的索引。

2>、$操作符如何使用索引:·低效率操作符:$where查询和({key:{$exists:true/false}})无法使用索引。在索引中,不存在字段和null字段的存储方式是一样的,查询必须遍历每一个文档检查这个值是否真的为null还是根本不存在。取反效率是比较低的,$ne查询可以使用索引,但是并不是很有效,因为必须要查看所有的索引条目,而不只是$ne指定的条目,不得不扫描整个索引。$not有时能够使用索引,但是通常它并不知道要如何使用索引,它能够对基本的范围(比如将{key:{$lt:7}}变成{key:{$gte:7}})和正则表达式进行反转。$nin总是进行全表扫描。

3>、范围:设计基于多个字段的索引时,应该将会用于精确匹配的字段放在索引的前面,将用于范围匹配的字段放在最后。这样,查询就可以先使用第一个索引键进行精确匹配,然后再使用第二个索引范围在这个结果集内部进行搜索。

4>、OR查询:mongodb在一次查询中只能使用一个索引,$or是个例外,$or可以对每个字句都是用索引,因为$or实际上是执行两次查询然后将结果集合并。例如:db.foo.find({$or:[{x:123},{y:456}]}).explain(),explain输出结果是两次独立的查询组成。通常来说,执行两次查询再讲结果合并的效率不如单次查询高,应该竟可能使用$in而不是$or。如果不得不使用$or,mongodb需要检查每次查询的结果集病且从中移除重复的文档(有些文档可能会被多个$or字句匹配到)。但是,使用$in查询时无法控制返回文档的顺序(除非进行排序)。

5>、索引对象和数组:mongodb允许深入文档内部,对嵌套字段和数组建立索引。嵌套对象和数组字段可以与复合索引中的顶级字段一起使用。1、索引嵌套文档:可以在嵌套文档的键上建立索引,方式于正常的键一样。如果有这样一个集合,其中的第一个文档表示一个,可能需要使用嵌套文档来表示每个用户的位置:{username:"sid",loc:{ip:"1.2.3.4",city:"bj",state:"TT"}},需要早loc的某一个字段(比如:loc.city)上建立索引,以便提高这个字段的查询速度:db.users.ensuerIndex({"loc.city":1}),可以用这种方式对任意深层次的字段建立索引。对嵌套文档本身(loc)建立索引,与对嵌套文档的某一个字段(loc.city)建立索引是不同的。对整个文档建立索引,只会提高整个文档的查询速度。如果在loc上建立索引,只有在进行与子文档顺序完全匹配的子文档查询时,查询优化器才会使用loc上的索引。2、索引数组:也可对数组建立索引,可以高效地搜索数组中的特定元素。对数组建立索引,实际上是对数组的每一个元素建立一个索引条目,因此数组索引的代价比单值索引的代价高:对于单词插入、更新或者删除,每一个数组条目可能都需要更新。无法将整个数组作为一个实体建立索引:对数组建立索引,实际上是对数组中的每个元素建立索引,而不是对数组本身建立索引,在数组上建立索引并不包含任何位置信息:无法使用数组索引查找特定的位置的数组元素。一个索引中的数组字段最多只能有一个,这是为了避免在多键索引条目爆炸性增长:每一对可能的元素都要被索引,这样导致每个文档拥有n*m个索引条目。3、多键索引:对于某个索引的键,如果这个键在某个文档中是一个数组,那么这个索引就会被标记为多键索引。可以使用explain()的输出查看一个索引是否为多键索引:如果使用了多键索引,isMultikey字段的值会是true,索引只要被标记为多键索引,就无法再变成非多键索引了,即使这个字段为数组的所有文档都从集合中删除。要将多键索引恢复为非多键索引,唯一的方法就是删除再重建这个索引。多键索引可能会比非多键索引慢,可能会有多个索引条目指向同一个文档,因此mongodb在返回结果集时必须要先去除重复内容。4、基数:就是集合中某个字段拥有不同值得数量。通常,一个字段的基数越高,这个键上的索引就越有用,这是因为索引能够迅速将搜索范围缩小到一个比较小的结果集。对于低基数的字段,索引通常无法排除掉大量可能的匹配。一般来说,应该在基数比较高的键上建立索引,或者至少应该把基数较高的键放在复合索引的前面(低基数的键之前)。

 6>、使用explain()和hint():对于速度比较慢的查询来说,explain()是最重要的诊断工具之一,通过查看一个查询的explain输出信息,可以知道查询使用了那些索引,以及是如何使用的。对于任意查询,都可以在最后添加一个explai()调用(与调用sort()或者limit()一样,不过explain()必须放在最后)。可以使用hint()强制mongodb使用特定的索引,如果查询没有使用你希望它使用的索引,于是你使用hint()强制mongodb使用某个索引,那么应该在应用程序部署之前在所指定的索引上执行explain(),如果强制mongodb在某个查询上使用索引,而这个查询不知道如何使用这个索引,这样会导致查询效率降低,还不如不使用索引来的快。

7>、查询优化器:如果一个索引能够精确匹配一个查询,那么查询优化器就会使用这个索引。不然的话,可能会有几个索引都适合你的查询,mongodb会从这些可能的索引子集中为每次查询计划选择一个,这些查询计划是并行执行的,最早返回100个的结果就是胜者,其他查询计划就会被中止。这个查询计划会被缓存,这个查询接下来都会使用它,直到集合数据发生了比较大的变动。如果在最初的计划评估之后集合发生了比较大的数据变动,查询优化器就会重新挑选可行的查询计划,建立索引时,或者是没执行1000次查询之后,查询优化器都会重新评估查询计划。explain()输出信息里的“allPlans”字段显示了本次查询尝试过得每个查询计划。在提取较小的数据集时,索引非常高效。结果集在原集合中所占的比例越大,索引的速度就越慢,因为使用索引需要进行两次查找:一次是查找索引条目,一次是根据索引指针去查找相应的文档。

8>、索引类型:创建索引时可以指定一些选项,使用不同的选项建立索引会有不同的行为。1、唯一索引:唯一索引可以确保集合的每一个文档的指定键都有唯一值。如果一个文档没有对应的键,索引会将其作为null存储,所以,如果对某个键建立了唯一索引,但插入了多个缺少索引键的文档,由于集合已经存在一个该索引键的值为null的文档而导致插入失败。例如:db.users.ensuerIndex({username:1},{unique:true})。有些情况下,一个值可能无法被索引,索引储桶(index bucket)的大小是有限制的,如果某个索引条目超出了它的限制,那么这个条目就不会包含在索引里。所有的字段都必须小于1024字节,才能包含到索引里,如果一个文档的字段由于太大不能包含在索引里,mongodb不会返回任何错误或者警告。即超出8kb大小的键不会受到唯一索引的约束,可以插入多个同样的8kb长的字符串。2、复合唯一索引:创建复合唯一索引时,单个键的值可以相同,但所有键的组合值必须是唯一的。3、去除重复:在已有的集合上创建唯一索引时可能会失败,因为集合中可能已经存在重复值,通常需要先对已有的数据进行处理,找出重复的值。在极少数情况下,可能希望直接删除宠你重复的值,创建索引是使用"dropDups"选项,如果遇到重复的值,第一个会被保留,之后的重复文档都会被删除。例如:db.user.ensureIndex({age:1},{unique:true,dropDups:true})。dropDups会强制性建立唯一索引,由于无法控制哪些文档被保留哪些文档被删除,因此要慎用。4>、稀疏索引:唯一索引会把null看做值,所以无法将多个缺少唯一索引中的键的文档插入到集合中。然而,在有些情况下,可能希望唯一索引只对包含相应键的文档生效,如果有一个可能存在也可能不存在的字段,但是当它存在时,他必须是唯一的,这时就可以将unique和sparse选项组合在一起使用。mongodb中的稀疏索引只是不需要将每个文档都作为索引条目。使用sparse选项就可以创建稀疏索引。例如,如果有一个可选的email地址字段,但是,如果提供了这个字段,那么他的值必须是唯一的:db.ensureIndex({emain:1},{unique:true,sparse:true})。稀疏索引不必是唯一的,只有去掉unique选项,就可创建一个非唯一的稀疏索引。

9>、索引管理:可以使用ensureIndex函数创建新的索引,对于一个集合,每个索引只需要创建一次,重复创建相同的索引,是没有任何作用的。所有的数据库索引信息都存储在system.indexes集合中。这是一个保留集合,不能再其中插入或者删除文档,只能通过ensureIndex或者dropIndexes对其进行操作。创建一个索引后,可以在system.indexes中查看他的元信息。可以执行db.collectionName.getIndexes()来查看给定集合上的所有索引信息。1、标识索引:集合中的每一个索引都有一个名词,用于唯一标识这个索引,也可以用于服务器端来删除或者操作索引。索引名称的默认形式是keyname1_dir1_keyname2_dir2_..._keynameN_dirN,其中,keynameX是索引的键,dirX是索引的方向(1或者-1)。也可以在ensureIndex中指定索引的名称:db.users.insureIndex({a:1,b:1},{name:test}),调用getLastError就可以知道索引是否成功创建,或者失败的原因。2、修改索引:可以使用dropIndex命令删除不再需要的索引,用索引描述信息里的name字段的值来指定需要删除的索引。新建索引是一件既费时又浪费资源的事情。默认情况下,mongodb会尽可能快地创建索引,阻塞所有对数据库的读请求和写请求,一直到索引创建完成。如果希望数据库在创建索引的同时任然能够处理读写请求,可以在创建索引时指定background选项。这样在创建索引时,如果有新的数据库请求需要处理,创建索引的过程就会暂停一下,但是这样任然会对应用程序性能有比较大的影响。后台创建索引比前台创建索引慢得多。在已有文档上创建索引会比新创建索引再插入文档快一点。

10>、固定集合(用于类队列数据):固定集合需要事先创建好,而且它的大小固定。当固定集合被占满时,如果再插入新文档,固定集合会自动将最老的文档从集合中删除以释放空间,新插入的文档会占据这块空间。固定集合的数据被顺序写入磁盘上的固定空间。固定集合可以用于记录日志。1、创建固定集合:固定集合必须在使用之前先显示创建,可以使用create命令创建固定集合。db.createCollection("my_collection",{capped:true,size:100000}),除了大小,createCollection还能够指定固定集合中文档的数量。db.createCollection("my_collection",{capped:true,size:100000,max:100}),固定集合创建后,就不能改变了(如果需要修改固定集合的属性,只能将它删除之后再重建)。为固定集合指定文档数量限制时,必须同时指定固定集合的大小,不管先达到哪一个限制,之后插入的新文档就会把最老的文档挤出集合;固定集合的文档数量不能超过文档数量限制,固定集合的大小也不能超过大小限制。可以将一个已有的某个常规集合转换为固定集合,可以使用convertToCapped命令实现,但是无法将固定集合转换为非固定集合(只能将其删除)。例如:db.runCommand({convertToCapped:"test",size:10000})。2、自然排序:对固定集合可以进行一种特殊的排序,称为自然排序。自然排序返回结果集中文档的顺序就是文档在磁盘上的顺序。固定集合中的文档是按照文档被插入的顺序保护的,自然顺序就是文档的插入顺序。自然排序得到的文档是从旧到新排列的,也可以按照从新到旧的顺序排列。例如:db.my_collection.find().sort({$natural:1/-1})。3、循环游标:是一种特殊的游标,当循环游标的结果集被取光后,游标不会被关闭。由于循环游标在结果集取光之后不会被关闭,因此,当有新文档插入到集合中时,循环游标会继续取到结果。由于普通集合并不维护文档的插入顺序,因此循环游标只能用在固定集合上。循环游标通常用于当文档被插入到固定集合时对新插入的文档进行处理。如果超过10分钟没有新的结果,循环游标就会被释放,因此,当游标被关闭时自动重新执行查询是非常重要的。4、没有_id索引的集合:默认情况下,每个集合都有一个_id索引,如果在调用createCollection创建集合时指定autoIndexId选项为false,创建集合时就不会自动在_id上创建索引。

 11>、TTL索引(time-to-live index,具有生命周期的索引):这种索引允许为每一个文档设置一个超时时间。一个文档到达预设值的老化程度之后就会被删除,这种类型的索引对于缓存问题(比如会话的保存)非常有用。在ensureIndex中指定expireAfterSecs选项就可以创建一个TTL索引:db.foo.ensureIndex({lastUpdated:1},{expireAfterSecs:60*60*24}),如果一个文档的lastUpdated字段存在并且它的值是日期类型,当服务器时间比文档的lastUpdated字段的时间晚expireAfterSecs秒时,文档就会被删除。为了防止活跃的会话被删除,可以在会话上有活动发生时将lastUpdated字段的值更新为当前时间。只要lastUpdated的时间距离当前时间达到24小时,相应文档就会被删除。mongodb每分钟对TTL索引进行一次清理,所以不应该依赖以秒为单位的时间保证索引的存活状态。可以使用collMod命令修改expireAfterSecs的值:db.runCommand({collMod:"someapp.cache",expireAfterSecs:3600}),在一个给定的集合上可以有多个TTL索引。TTL索引不能是复合索引,但是可以像“普通”索引一样来优化排序和查询。

12>、全文本索引:一个特殊类型的索引,用于在文档中搜索文本,使用全文本索引可以非常快的进行文本搜索。在一个操作频繁的集合上创建全文本索引可能会导致mongodb过载,所以应该是离线状态下创建全文本索引,或者是在对性能没要求时。

13>、地理空间索引:mongodb支持集中类型的地理空间索引。其中最常用的是2dsphere索引(用于地球表面类型的地图)和2d索引(用于平面地图和时间连续的数据)。2dsphere允许使用GeoJSON格式指定点、线和多边形。点可以用形如[longitude,latitude]([经度,纬度])的两个元素的数组表示:{name:"New York City",loc:{type:Point,coordinates:[50,2]}},线可以用一个由点组成的数组来表示:{name:"Hudson River",loc:{type:Line,coordinates:[[0,1],[0,2]]}},多边形的表示方式与线一样(都是一个由点组成的数组),但是type不同。在ensureIndex中使用“2dsphere”选项就可以创建一个地理空间索引:db.world.ensureIndex({loc:2dsphere})。1、地理空间查询的类型:可以使用多种不同类型的地理空间查询:交集(intersection)、包含(within)以及接近(nearness)。查询时,需要将希望查找的内容指定为形如{"$geometry":geoJsonDesc}的GeoJSON对象。例如,可以使用$geoIntersects操作符找出与查询位置相交的文档:var eastVillage={type:Polygon,coordinates:[[-73.9917900,40.7264100],[-73.9917900,40.7321400]]},db.open.street.map.find({loc:{$geoIntersects:{$geometry:eastVillage}}})就会找到所有与eastVillage区域有交集的文档。可以使用$within查询完全包含在某个区域的文档,db.open.street.map.find({loc:{$within:{$geometry:eastVillage}}})。可以使用$near查询附近的位置:db.open.street.map.find({loc:{$near:{$geometry:eastVillage}}}),$near是唯一一个会对查询结果进行自动排序的地理空间操作符,$near的返回结果是按照距离由近及远排序的。地理位置查询有一点非常有趣:不需要地理空间索引就可以使用$geoIntersects或者$within($near需要使用索引)。但是,建议在用于表示地理位置的字段上建立地理空间索引,可以显著的提高查询速度。2、复合地理空间索引:如果有其他类型的索引,可以将地理空间索引与其他字段组合在一起使用,以便对更复杂的查询进行优化。例如db.open.street.map.ensureIndex({tags:1,locction:2dsphere}),其他索引字段可以放在2dsphere前面,也可以放在后面,这取决于我们希望首先使用其他索引的字段进行过滤还是首先使用位置进行过滤,应该将那个能够过滤掉尽可能多的结果的字段放在前面。db.open.street.map.find({loc:{$within:{$geometry:eastVillage}},tags:"pizza"})。3、2d索引:对于非球面地图(游戏地图、时间连续的数据等),可以使用2d索引代替2dsphere,2d索引用于扁平表面,而不是球体表面。2d索引不应该用在球体表面上,否则极点附近会出现大量的扭曲变形。2d索引只能对点进行索引,可以保存一个由点组成的数组,但是它只会被保存为由点组成的数组,不会被当成线。

14>、使用GridFS存储文件:GridFS是mongodb的一种存储机制,用来存储大型二进制文件。通常来说,如果你有一些不常改变但是经常需要连续访问的大文件,那么使用GridFS再合适不过了。mongofiles三种基本操作:put、list和get。put操作可以将文件系统中选定的文件上传到GridFS;list操作可以列出GridFS中的文件;get操作与put相反,用于将GridFS中的文件下载到文件系统中。mongofiles还支持另外两种操作:用于在GridFS中搜索文件的search操作和用于从GridFS中删除文件的delete操作。GridFS是一种轻量级的文件存储规范,用于存储mongodb中的普通文档。GridFS背后的理念:可以将大文件分割为多个比较大的块,将每个块作为独立的文档进行存储。除了将文件的每一个块单独存储之外,还有一个文档用于将这些块组织在一起并存储该文件的元信息。GridFS中的块会被存储到专用的集合中,块默认使用的集合是fs.chunks,不过可以修改为其他集合。在块集合内部,各个文档的结构非常简单:{"_id":ObjectId("..."),n:0,data:BinData("..."),"files_id":ObjectId("...")}。files_id:块所属文件的元信息,n:块在文件中的相对位置,data:块所包含的二进制数据。每个文件的元信息被保存在一个单独的集合中,默认情况下这个集合是fs.files。这个文件集合中的每一个文档表示GridFS中的一个文件,文档中可以包含与这个文件相关的任意用户自定义元信息。

 

posted on 2016-12-23 13:55  一日三省吾身  阅读(259)  评论(0编辑  收藏  举报