MongoDB 索引

标签: mongodb 索引 | 发表时间:2012-08-01 09:39 | 作者:refactor
出处:http://www.cnblogs.com/

索引是用来加快查询的,数据库索引与数据的索引类似,有了索引就不需要翻遍整本书,数据库可以直接在索引中查找,

使得查询速度很快,在索引中找到条目后,就可以直接跳转到目标文档的位置.

1.索引简介

要掌握如何为查询配置最佳索引会有些难度.

MongoDB索引几乎和关系型数据库的索引一样.绝大数优化关系型数据库索引的技巧同样适用于MongoDB.

如:

db.refactor.insert({"username":"refactor","age":24,"isactive":true})
db.refactor.insert({"username":"refactor","age":30,"isactive":false})
db.refactor.insert({"username":"aaaaa","age":24,"isactive":false})
db.refactor.insert({"username":"aaaaa","age":34,"isactive":true})
db.refactor.insert({"username":"sssssss","age":24,"isactive":true})
db.refactor.insert({"username":"tttttt","age":24,"isactive":true})
db.refactor.insert({"username":"tttttt","age":54,"isactive":true})
db.refactor.insert({"username":"bbbbb","age":24,"isactive":false})
db.refactor.insert({"username":"rrrrr","age":24,"isactive":true})
db.refactor.insert({"username":"rrrrr","age":54,"isactive":false})

 

要按照username键进行查找,就可以在此键上建立索引,来提高查询速度.

db.refactor.ensureIndex({"username":1})

对某个键创建索引会加速对该键的查询,但是对于其他的查询可能没有帮助,即便查询中包含了被索引的键.

db.refactor.find({"age":24}).sort({"age":1,"username":1})

不会用到username索引.服务器必须查找所有文档,找到想要的日期,这个过程叫:表扫描,就是在没有索引的书中查找

内容,要从第一页开始,从前翻到后.通常说,应避免让服务器做表扫描,因为集合很大时会很慢.

一定要创建查询中用到的所有键索引,对于上面的查询,应该建立age和username的索引.

db.refactor.ensureIndex({"age":1,"username":1})

传递给ensureIndex的文档是一组值为1或-1的键,表示索引的创建方向.若索引只有一个键,则方向无关紧要.

若是有多个键,就得考虑索引的方向问题了.

如:

> db.runCommand({"dropIndexes":"refactor","index":"*"})
{
"nIndexesWas" : 2,
"msg" : "non-_id indexes dropped for collection",
"ok" : 1
}
> db.refactor.ensureIndex({"username":1,"age":1})
> db.refactor.ensureIndex({"username":1,"age":-1})
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.blog", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.refactor", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "test.users", "name" : "_id_" }
{ "v" : 1, "key" : { "username" : 1, "age" : 1 }, "ns" : "test.refactor", "name"
: "username_1_age_1" }
{ "v" : 1, "key" : { "username" : 1, "age" : -1 }, "ns" : "test.refactor", "name
" : "username_1_age_-1" }

如果以{"username":1,"age":1}这种方式创建索引,MongoDB会按如下方式组织:

> db.refactor.find().hint({"username":1,"age":1})
{ "_id" : ObjectId("500231f4218b8ef3edbc6f00"), "username" : "aaaaa", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f01"), "username" : "aaaaa", "age" : 34
, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f05"), "username" : "bbbbb", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6efe"), "username" : "refactor", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6eff"), "username" : "refactor", "age" :
30, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f06"), "username" : "rrrrr", "age" : 24
, "isactive" : true }
{ "_id" : ObjectId("500231f6218b8ef3edbc6f07"), "username" : "rrrrr", "age" : 54
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f02"), "username" : "sssssss", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f03"), "username" : "tttttt", "age" : 2
4, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f04"), "username" : "tttttt", "age" : 5
4, "isactive" : true }

用户名按照字母升序排列,同名的组按照年龄升序排列.

如果以{"username":1,"age":-1}这种方式创建索引,MongoDB会按如下方式组织:

> db.refactor.find().hint({"username":1,"age":-1})
{ "_id" : ObjectId("500231f4218b8ef3edbc6f01"), "username" : "aaaaa", "age" : 34
, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f00"), "username" : "aaaaa", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f05"), "username" : "bbbbb", "age" : 24
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6eff"), "username" : "refactor", "age" :
30, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6efe"), "username" : "refactor", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f6218b8ef3edbc6f07"), "username" : "rrrrr", "age" : 54
, "isactive" : false }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f06"), "username" : "rrrrr", "age" : 24
, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f02"), "username" : "sssssss", "age" :
24, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f04"), "username" : "tttttt", "age" : 5
4, "isactive" : true }
{ "_id" : ObjectId("500231f4218b8ef3edbc6f03"), "username" : "tttttt", "age" : 2
4, "isactive" : true }

用户名按照字母升序排列,同名的组按照年龄降序排列.

 

一般来说,如果索引包含了N个键,则对于前几个键的查询都能利用索引,如:有个索引{"a":1,"b":1,"c":1,"d":1}

实际上是有了{"a":1},{"a":1,"b":1},{"a":1,"b":1,"c":1}索引,但是使用{"b":1},{"a":1,"c":1}等索引的查询不会被优化.

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

 

MongoDB的查询优化器会从排查询项的顺序,以便利用索引,如查询{"username":"refactor","age":24}的时候,已经有了

{"age":1,"username":1}的索引,MongoDB会自己找到并利用它.

创建索引的缺点是每次插入,更新,删除都会产生额外的开销,因为数据库不但需要执行这些操作,还要将这些操作在集合的索引中

标记.因此,尽可能少的创建索引.

有些时候,最有效的查询是不实用查询,一般来说,要是查询要返回集合中一半以上的结果,用表扫描会比几乎每条文档都要

索引要快,所以,查询是否存在某个键,或者检查摸个布尔类型的值是真是假,就没有必要利用索引.

 

2.扩展索引

假设有个集合存储了用户的状态信息.现在要查询用户和日期,取出某一用户最近的状态.我们可能会建立

如下索引:

db.users.ensureIndex({"user":1,"date":-1})

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

因为应用会有数百万的用户,每人每天都有数十条状态更新.若是每条用户状态的索引值咱用类似一页纸的

磁盘控件,那么对每次"最新状态"的查询,数据库将会将不同的页载入内存.若是站点太热门,内存放不下所有

索引,就会很慢.要是改变索引的顺序{"date":-1,"user":1},则数据库可以将最后几天的索引保存在内存中,

可以有效的减少内存交换,这样查询任何用户的最新状态都会快很多.

 

3.索引内嵌文档中的键

为内嵌文档的键创建索引和为普通的键创建索引没有什么区别.

db.blog.insert(
  {
    "title":"refactor's blog",
    "Content":"refactor's blog test",
    "author":
    {
      "name":"refactor",
      "email":"[email protected]"
    }  
  }
)

为author.name创建索引

db.blog.ensureIndex({"author.name":1})

对内嵌文档的键索引和普通键索引没有区别,两者可以联合组成复合索引.

 

3.为排序创建索引

随着集合的增长,需要针对查询中大量的排序做索引.如果对没有索引的键调用sort,MongoDB需要将所有数据

提取到内存中来排序.因此,可以做无索引排序是有个上限的,即不可能在内存中对T级别的数据排序.按照排序来索引

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

 

4.索引名称

集合中的每个索引都有一个字符串类型的名字,来唯一标识索引,服务器通过这个名字来删除或操作索引.默认情况下,

索引名类似 keyname1_dir1_keyname2_dir2这种形式,其中keyname代表索引的键,dir代表索引的方向(1或-1).

可以通过ensureIndex来指定索引的名称.

如:

db.blog.ensureIndex({"author.name":1},{"name":"author_name_index"})

注意不能修改,只能删除索引,再重建.

索引名有字符个数的限制,所以特别复杂的索引在创建时一定要使用自定义的名字,可以用getLastError来检查索引

是否成功创建了或未创建成功的原因.

 

5.唯一索引

唯一索引可以确保集合的每一个文档的指定键都有唯一值.如果想保证文档的username键都有不同的值:

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

默认情况下,insert并不检查文档是否插入过了.所以为了避免插入的文档包含与唯一键重复的值,可能要用安全插入

才能满足要求,这样,在插入这样的文档会看到存在重复键错误的提示.

注意,如果文档中没有对应的键,索引会认为它是以null存储的,所以,如果对某个键建立了唯一索引,但插入了多个

缺少该索引键的文档,这由于文档包含null值而导致插入失败.

 

6.消除重复

当为已有的集合创建唯一索引,可能有些值已经重复了.这样唯一索引将创建失败.但是,可能希望将所有包含重复值

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

db.refactor.ensureIndex({"username":1},{"unique":true,"dropDups":true})

如果有重要数据的话,最好还是写个脚本预处理,而不是设置dropDups

 

7.复合唯一索引

创建复合唯一索引,单个键的值可以重复,只要所有键的值组合起来不同就行.

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

 

8.使用explain和hint

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

explain会返回一个文档,而不是游标本身,这是与多数游标方法不同之处.

"cursor":"BtreeCursor age_1_username_1"

说明查询使用了age_1_username_1索引.

"nscanned" : 6

6 代表数据库查找了多少个文档.
"n" : 6

这个代表返回文档的数量
"millis" : 0

这个毫秒数表示数据库执行查询的时间.

可以通过索引名字age_1_username_1,来获取索引的详细信息.

db.system.indexes.find({"ns":"test.refactor","name":"age_1_username_1"})

 

如果 refactor集合有如下两个集合:

db.refactor.ensureIndex({"username":1,"age":1})
db.refactor.ensureIndex({"age":1,"username":1})

要查询用户的用户名和年龄:

db.refactor.find({"age":{"$gt":30},"username":"refactor"}).explain()

这个会用"username":1,"age":1的索引,因为是要求精确查询用户名和年龄范围,数据库自己调换了查询项的顺序.
db.refactor.find({"age":24,"username":/.*/}).explain()

这个会用"age":1,"username":1的索引

 

如果发现MongoDB用了非预期的索引,可以用hint强制使用某个索引.如:

db.refactor.find({"age":{"$gt":30},"username":"refactor"}).hint({"age":1,"username":1}).explain()

多说情况下,这种指定没有必要,MongoDB的查询优化器很智能,会替你选择用哪个索引.初次做某个查询时,

查询优化器会同时尝试各种查询方案.最先完成的被确定使用,其他的则终止掉.查询方案被记录下来,以备日后

应对相同键的查询.查询优化器定期重试其他方案,以防止因为添加新数据后,之前的方案不是最优了.只要关心

给查询优化器建立可以选择的索引就可以了.

 

9.索引管理

索引的元信息存储在每个数据库的system.indexes集合中.这是一个 保留集合(遍历数据库中所有集合时要小心,因为

通常我们不想对这个集合进行操作),不能对其插入或删除文档.操作只能通过ensureIndex或dropIndexes进行.

system.indexes集合中包含每个索引的详细信息.system.namespaces集合包含索引的名字.

 

10.修改索引

随着应用程序的使用,数据库的数据或查询发生了改变,原来的索引不在使用.可以使用ensureIndex随时向数据库

添加新的索引.

db.refactor.ensureIndex({"username":1,"age":1},"background":true)

建立索引即耗时又费力,还要消耗更多资源.使用{"background":true}选项可以使这个过程在后台完成

,同时正常处理请求.要是不使用background这个选项,数据库会阻塞建立索引期间的所有请求.

阻塞的做法会使索引建立的更快.即使在后台创建索引也会对正常操作有影响,所以最好选择无关紧要的时间.

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

要是索引没用了,可以使用dropIndexes加上索引名称将其删除.通常,要查一下system.indexes集合来找出索引名,

以为自动生成的名字会因驱动程序的不同而不同.

db.runCommand({"dropIndexes":"blog","index":"author.name_1"})

要删除所有索引

db.runCommand({"dropIndexes":"blog","index":"*"})

 

11.地理空间索引

随着移动设备的出现,找到离当前位置最近的N个场所的查询越来越多.MongoDB为坐标平面查询提供了专门

的索引,称作 地理空间索引

地理空间索引也是使用ensureIndex来创建,只不过不是"1"或"-1",而是"2d"

db.map.insert({"gps":[1,100]})
db.map.insert({"gps":{"x":-30,"y":30}})
db.map.insert({"gps":{"latitude":-60,"longitude":30}})

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

"gps"键的值必须是某种形式的一对值:一个包含两个元素的数组或者是包含两个键的内嵌文档.内嵌文档

的键名可以是随意的,如{"gps":{"refactor":-60,"refactor1":30}

默认情况下,地理空间索引的值是-180~180(对经纬度很方便).要是想用其他值

db.map.ensureIndex({"gps":"2d"},{"min":-1000,"max":1000})

这样就创建了一个2000光年见方的空间索引.

地理空间查询有两种方式:

db.map.find({"gps":{"$near":[49,-49]}})

这会按照点(49,-49)由近及远的方式将map集合的所有文档返回.在没有指定limit值时,默认是100个文档.

要是不需要那么多结果,就应该设置一个少点的值以节约资源.

db.map.find({"gps":{"$near":[49,-49]}}).limit(1)

也可以使用:

db.runCommand({geoNear:"map",near:[49,-49],num:1})

geoNear还会返回每个文档到查询点的距离.这个距离是以你插入的数据为单位的,如果按照经纬度的角度插入,则

距离就是经纬度.find和"$near"组合不会给出距离,但若是结果大于4M,这是唯一的选择.

 

MongoDB不但能找到靠近一个点的文档,还能找到指定形状内的文档.做法是将原来的"$near"换成"$within".

"$within"获取形状作为参数.这些形状可以是 矩形,圆形等.

对于矩形:

db.map.find({"gps":{"$within":{"$box":[[10,20],[15,30]]}}})

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

对于圆形:

db.map.find({"gps":{"$within":{"$center":[[12,25],5]}}})

 

12.复合地理空间索引

应用程序要找的东西经常不只是一个地点.可以将地理空间索引与普通索引组合起来.

MongoDB的地理空间索引假设索引的内容是在一个平面上的,也就是说,对于球体的地球,并不是很精确. 

本文链接

相关 [mongodb 索引] 推荐:

MongoDB 索引

- - 博客园_首页
索引是用来加快查询的,数据库索引与数据的索引类似,有了索引就不需要翻遍整本书,数据库可以直接在索引中查找,. 使得查询速度很快,在索引中找到条目后,就可以直接跳转到目标文档的位置.. 要掌握如何为查询配置最佳索引会有些难度.. MongoDB索引几乎和关系型数据库的索引一样.绝大数优化关系型数据库索引的技巧同样适用于MongoDB..

MongoDB索引内部实现

- Rehtron - NoSQLFan
在数据库优化中,索引优化是非常重要也是每一个数据库使用者必须了解的. MongoDB的索引采用BTree结构,除了其地理位置索引外,数据索引本质上和MySQL 的 BTree索引没有什么差别. 下面是一个接近200页的PPT,对MongoDB读写操作相关的索引操作进行了图文讲解. 如果你对数据库索引的实现原理感兴趣,可以仔细看看.

MongoDB索引实战技巧

- - NoSQLFan
本文内容源自 Kyle Banker 的 MongoDB In Action一书. 主要描述了MongoDB 索引相关的一些基础知识和使用技巧. 虽然MongoDB的索引在存储结构上都是一样的,但是根据不同的应用层需求,还是分成了唯一索引(unique)、稀疏索引(sparse)、多值索引(multikey)等几种类型.

MongoDB中索引的用法

- - 数据库 - ITeye博客
本文是一篇转载文章,作者在对 MongoDB文档进行了细致的阅读后,总结出了MongoDB的各种索引的用法. 原文链接: http://iamcaihuafeng.blog.sohu.com/151638529.html. 索引能提高检索数据的速度,你可以想像成在MySQL中创建索引一样,同样索引也是用B-Tree也实现的.

关于Mongodb索引实战

- - snoopyxdy的博客
最近碰到这样的一个需求,一张酒店政策优惠表,我们要根据用户入住和离开的时间,计算一家酒店的最低价政策前10位,数据库表字段如下:. 'hid':88,     酒店id. 'date':20150530,  入住日期整形(不要纠结unix时间戳). 'enable':1,  政策是否启用. 'price':100, 政策价格.

mongodb索引讲解与性能调优

- - haohtml's blog
mongodb索引规则基本上与传统的关系库一样,大部分优化MySQL/Oracle/SQLite索引的技巧也适用于mongodb. 当查询中用到某些条件时,可以对该键建立索引,以提高查询速度. 如果数据量很多且查询多于更新时,可以用索引提高查询的速度. a)         查询索引:. 查询索引很简单,比如说需要查询mailaccess数据库中的Mail collection上的索引时:.

Dex – MongoDB索引优化工具

- - NoSQLFan
Dex是一个开源的MongoDB 优化工具,它通过对查询日志和当前数据库索引进行分析,向管理员提出高效的索引优化策略. 在监控过程中,dex会通过stderr输出推荐的结果. 我们看到,在输出结果中,有一个shellCommand字段,里面就是添加索引的语句,如果你觉得dex的推荐不错,就可以直接复制这段脚本在 MongoDB上添加索引了.

MongoDB范围查询的索引优化

- - NoSQLFan
我们知道, MongoDB的 索引是B-Tree结构的,和MySQL的索引非常类似. 所以你应该听过这样的建议:创建索引的时候要考虑到sort操作,尽量把sort操作要用到的字段放到你的索引后面. 但是有的情况下,这样做反而会使你的查询性能更低. 比如我们进行下面这样的查询:. 查询条件是 {“country”: “A”},按 carsOwned 字段的正序排序.

[mongodb] java操作mongodb

- - 数据库 - ITeye博客
           //实例化Mongo对象,连接27017端口.                               //连接名为yourdb的数据库,假如数据库不存在的话,mongodb会自动建立. //从Mongodb中获得名为yourColleection的数据集合,如果该数据集合不存在,Mongodb会为其新建立.

【MongoDB】MongoDB之优化器Profiler

- - CSDN博客数据库推荐文章
在mysql数据库中,慢查询日志经常作为优化数据库的依据, mongodb中依然有类似的功能. Mongodb自带的profiler,可以方便地记录所有耗时的操作,以便于调优;. 一、开始profiler功能. 开启profier功能有两种:. 第一种就是直接在启动参数里面进行设置,就在茄冬mongodb时候添加-profile=级别.