• MongoDB 索引


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

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

    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":"295240648@163.com"
        }  
      }
    )

    为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的地理空间索引假设索引的内容是在一个平面上的,也就是说,对于球体的地球,并不是很精确. 

  • 相关阅读:
    【七款炫酷的页面特效】
    【PHP环境-WampServer踩坑】
    【Elasticsearch在winodws系统启动报could not find java+闪退】
    【Vue-入门笔记-7】
    关于ios的光标和键盘回弹问题
    AES加密然后ajax传输数据
    文件进行MD5计算
    jqGrid 常用 总结 -2
    关于页面传参,decodeURI和decodeURIComponent
    js防抖和节流
  • 原文地址:https://www.cnblogs.com/refactor/p/2592191.html
Copyright © 2020-2023  润新知