简介
索引就是用来加速查询的。数据库索引与书籍的索引类似:有了索引就不需要翻遍整本书,数据库则可以直接在索引中査找,使得查找速度能提高几个数量级。在索引中找到条目以后,就可以直接跳转到目标文档的位置。让这个比喻走个极端,可以说创建数据库索引就像确定如何组织书的索引一样。但你的优势是知道今后会做何种査询,以及哪些内容需要快速査找。索引能够提升查询效率.没有索引,MongoDB必须扫描集合中的所有文档才能找到匹配查询语句文档,索引是一种特殊的数据结构,将一小块数据集保存为容易遍历的形式.索引能够存储某种特殊字段或字段的值,按照索引指定的方式可以将字段值进行排序,对于添加的每一个索引,每一次插入更新,删除都将消耗更多的时间,因为数据发生改变的时候不仅仅是更新文档,还有更新集合上的所有索引.MongoDB限制每个集合上的最多有64个索引,使用索引键对文档进行排序时可以提升排序效率,索引时按照一定顺序排列的,,索引才有效果,索引类型有很多包括 单字段索引、复合索引、多支索引、地理空间索引、文本索引等等.
创建索引
> db.collection.ensureIndex(<keys>,<options>) #3.0版本
keys 希望创建索引的名称和排序方式1表示升序,-1表示降序排列,格式{key:1, key-1}如果时基于多个索引键进行排序,要特别注意索引的排序方向,相同的排序方向可以提升排序效率,单个键排序时没有影响。
options:可选参数,表示建立索引的设置:
参数 | 数据类型 | 默认值 | 功能 |
---|---|---|---|
background | Boolean | false | 后台建立索引,以便创建索引时不阻止其他数据库活动 |
unique | Boolean | false | 创建唯一索引 |
name | String | 指定索引名称,如果未指定,MongoDB会生成一个索引字段的名称和排序顺序串联 | |
partialFilterExpression | document | 如果指定MongoDB指挥满足过滤表达式的记录 | |
sparse | Boolean | false | 对文档中不的存在的字段数据不启用索引 |
expireAfterSeconds | integer | 指定索引的过期时间 | |
storageEngine | document类型允许用户配置索引的额存储引擎 |
删除索引
方式一
删除当前集合中的所有索引(_id上默认索引除外)
db.collection.dropIndexes()
方式二
db.collection.dropIndex(index)
在索引上找到条目之后,就可以直接跳到目标文档的位置
在user中有5个文档,其中存储信息如下
{ "_id" : ObjectId("5c0df99fbc6d47cbcdb55fd0"), "name" : "jack", "age" : 19 } { "_id" : ObjectId("5c0df9abbc6d47cbcdb55fd1"), "name" : "rose", "age" : 20 } { "_id" : ObjectId("5c0df9b0bc6d47cbcdb55fd2"), "name" : "jack", "age" : 18 } { "_id" : ObjectId("5c0df9c3bc6d47cbcdb55fd3"), "name" : "tony", "age" : 21 } { "_id" : ObjectId("5c0df9cdbc6d47cbcdb55fd4"), "name" : "adam", "age" : 18 }
如果有个查询
> db.user.find({age:18})
这个时候需要遍历所有的文档,查询所有的文档,根据位置信息读出文档,对比age字段是否为18。当然入股偶只有5个文档,全表扫描的开销并不大,简单如果集合文档数量到百万,千万或者上亿的时候,对集合进行去哪表扫描的开销时非常大的,一个查询耗费数十秒甚至几分钟都可能。
如果想加速
> db.user.find({age:18})
可以对user表的age字段孙银
按照age字段创建升序索引
> db.user.createIndex({age:1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 }
建立索引之后,MongoDB会额外存储一份按照ae字段升序排序的索引数据,索引通常采用类似于btree的结构持久化存储
以保证从索引里快速找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。
简单的说,索引就是将文档按照某个(或者某些)字段顺序组织起来,以便能根据该字段高效的查询。
唯一索引
MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字端),_id时文档的唯一表示,为了保证能够根据文档id快速查询文档,MongoDB默认会为集合创建_id的字段索引,唯一索引可以确保集合的每一个文档的指定键都有唯一值。如果想保留文档的“name”键都有不一样的值,创建一个唯一索引就好了:
db.user.ensureIndex({name:1},{unique:true})
由于insert并不健查文档时否查如果,为了避免插入的文档包含与唯一键重复i的值,可能要用安全插入才能满足要求。这样,在插入文档时会看到存在重复键的错误的提示。
消除重复
当为已有的索引创建索引,可能有些之已经有重复了。这时候创建所以会失败,我们希望将所有包含重复值的文档都删掉。dropDups选项就可以奥六发现的第一个文档,二删除接下来有重复的键值文档,如“name”键重复的索引值
db.user.ensureIndex({name:1},{unique:true,dropDups:true})
通常做开发不建议删除操作。
复合唯一索引
符合索引就是建立在多个字段上的索引 例如:
> db.user.ensureIndex({age:1,name:1})
上述符合索引由“age” “name” 组成的索引
复合唯一索引就是允许单个的键的值相同,但是所有键的组合之组合起来的不同索引
{ "age" : 2, "name" : 1 } { "age" : 2, "name" : 2 } { "age" : 2, "name" : 3 }
所有的“age”键都相同,但是“name“键的值不同,如果尝试再次插入
{ "age" : 2, "name" : 1 }
数据库会体时存在重复键的错误。
索引管理
索引的元信息存储在每个数据库的system.indexes集合中。这是一个保留集合不能对其插入或者删除文档。操作只能通过ensureIndex或者droplndexes进行。
查询索引明细
> db.user.getIndexes() [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "han.user" }, { "v" : 2, "key" : { "age" : 1 }, "name" : "age_1", "ns" : "han.user" }, { "v" : 2, "key" : { "age" : 1, "name" : 1 }, "name" : "age_1_name_1", "ns" : "han.user" }, { "v" : 2, "key" : { "age" : 2, "name" : 1 }, "name" : "age_2_name_1", "ns" : "han.user" } ]
查询索引的大小
> db.user.totalIndexSize() 118784(单位:字节) >
修改索引
MongoDB没有单独的修改索引方法,如果要修改某个索引,需要先删除旧的索引,再创建新的索引,
随着数据量的增长你会发现某个collection需要修改索引或者增加索引,此时创建索引就会很费力,同时也比较消耗性能。创建索引时MongoDB默认是阻塞式,会让索引建立更快,任何请求都不能相应。可以使用{ background:True } 选项再后台完成,同也可能正常处理请求。不过这种方式也会造成请求的响应很慢,如果再非紧急的情况下,最好晚上业务量少的时候统一处理。
地理空间索引
还有一种査询变得越来越流行(尤其是随着移动设备的出现):找到离当前位置最近的N个场所。MongoDB为坐标平面査询提供了专门的索引,称作地理空间索引。假设要找到给定经纬度坐标周围最近的咖啡馆,就需要创建一个专门的索引来提髙这种査询的效率,这是因为这种査询需要搜索两个维度。地理空间索引同样可以由ensurelndex来创建,只不过参数不是1或者-1,而是2d”:
db.map.ensurelndex ({ "gps":"2d"})
“gps”键的值必须是某种形式的一对值:一个包含两个元素的数组或是包含两个键的内嵌文档。下面这些都是有效的:
{ "gps" : [0, 100 ] } { "gps" : { "X" : -30, "y" : 30 } } { "gps,1 : { "latitude” : -180, "longitude” : 180 } }
至于键名可以随意,例如
"gps" : {"foo" : 0, "bar" : 1}}
也是可以的。
默认情况下,地理空间索引假设值的范围是-180 180 (对经纬度来说很方便)。要是想用其他值,可以通过ensurelndex的选项来指定最大最小值:
db.star.trek. ensurelndex ({" light-years” : ,,2d,'}, {"min" : -1000,"max" : 1000})
这样就创建了一个2000光年见方的空间索引。
地理空间査询以两种方式进行,即普通査询(用find)或者使用数据库命令。find的方式与一般的查询差别不大,只不过用了”$near”。需要两个目标值的数组作为参数:
db.map.find{ {"gps1':{”$near" : [40,-73]}})
这会按照离点(40, -73)由近及远的方式将map集合的所有文档都返回。在没有指定limit的值时,默认是100个文档。要是不需要这么多结果,就应该设置一个少点的值以节约资源。例如,下面的例子将返回离(40, -73)最近的10个文档。
db.map.find({ngps" : {"$near" :[40, -73]}}).limit(10)
也可以用geoNear来完成相同的操作:
db.runCommand({geoNear : "map", near : [40, -73], num : 10});
geoNear还会返回每个文档到査询点的距离。这个距离是以你插入的数据为单位的,如果按照经纬度的角度插入,则距离就是经纬度。find与”$near”的组合不会给出距离,但若是结果大于4MB,这是唯一的选择
MongoDB不但能找到靠近一个点的文档,还能找到指定形状内的文档。做法就是将原来的nnearn换成”
within”。n$within”获取数量不断增加的形状作为参数。若查看地理空间索引的联机帮助文档(http://www.mongodb.org/display/DOCS/Geospatial+ Indexing),可以找到最新的形状列表。有两个选项:你可以査询矩形和圆形内的所有点。
对于矩形,使用”$box”选项:
db.map.find({HgpsM : {"$withinn : {"$box" : [[10, 20], [15, 30]]}}})
”$box”参数是两个元素的数组,第一个元素指定了左下角的坐标,第二个指定右上角的坐标。
同样,也可以用“”$center”来找到圆形内部的所有点,只不过参数变成了圆心和半径:
db.map.find ({"gps" : {"$within” : {"$center" : [[12,25], 5] }}})
复合地理空间索引
应用经常要找的东西往往不只是一个地点。例如,用户要找出周围所有的咖啡店或者披萨店。将地理空间索引与普通索引组合起来就可以满足这种需求。例如,要査询”location”和”descn,就可以这样创建索引:
db.ensurelndex ({ " location11 : "2d", "desc" : 1})
然后就能很快找到最近的咖啡馆了:
> db.map.find {{location: {"$near" : [-70, 30]}, "desc": "coffeeshop”}).limit(1) { "_id” : Objectld(”4c0dl348928a815a720a0000"), "name" : "Mud", "location" : [x, y], "desc" : ["coffee","coffeeshop", "muffins", "espresso"] }
地球不是二维平面
MongoDB的地理空间索引假设索引内容是在一个平面上的。这就意味着对于球体,比如地球,它并不是十分精确,尤其是在极地区域。具体来说,两条经线之间纬线的长度在赤道和在育空地区1是大不一样的,后者要短得多。可以用不同的投影手段将地球映射到二维平面,当然它们的精度和相应的复杂度各不相同。