• MongoDB系列四(索引).


    一、索引简介

        再来老生常谈一番,什么是索引呢?数据库索引与书籍的索引类似。有了索引就不需要翻整本书,数据库可以直接在索引中查找,在索引中找到条目以后,就可以直接跳转到目标文档的位置,这能使查找速度提高几个数量级。

        然而,使用索引是有代价的:对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为,当数据发生变动时,MongoDB不仅要更新文档,还要更新集合上的所有索引。因此,MongoDB限制每个集合上最多只能有64个索引。通常,在一个特定的集合上,不应该拥有两个以上的索引。于是,挑选合适的字段建立索引非常重要。

    • 索引基数

    基数(cardinality)就是集合中某个字段拥有不同值的数量。比如 gender 字段,基数一般就男女 2个而已;而像 mobile 这样的字段,基数就会特别大。

    通常来讲,一个字段的基数越高,这个字段上的索引就越有用。这是因为索引能够迅速将搜索范围缩小到一个比较小的结果集。对于低基数的字段,索引通常无法排除掉大量可能的匹配。假设我们在"gender"上有一个索引,需要查找名为Susan的女性用户。通过这个索引,只能将搜索空间缩小到大约50%。

    tips在关系型数据库中类似 gender 这样的字段可以使用位图索引。

    • 索引原理浅析

    我们以一个索引 {"age" : 1"username" : 1} 来看看索引在MongoDB 中是如何存储的,大致是这个样子:

    每一个索引条目都包含一个"age"字段 和 "username"字段,并且指向文档在磁盘中的存储位置。注意,这里的 age 严格的按照升序排序,并且相同的 age 对应的 username 也严格的按照升序排序。

    来看个例子 :db.users.find({"age" : 21}).sort({"username" : -1})

    这个索引对于这个查询来说是非常高效的,因为它可以马上定位到 age = 21 的位置,并且age = 21 中的 username 已经是排序好的。

    tips排序方向并不重要:MongoDB可以在任意方向上对索引进行遍历。
    tips查询中的字段顺序无关紧要,MongoDB 会自动找出可以使用索引的字段,而无视查询的字段顺序。

    • $操作符如何使用索引

    有一些查询完全无法使用索引,也有一些查询能够比其他查询更高效地使用索引。

    $where:无法使用索引。
    $nin:无法使用索引。
    $exists:无法使用索引。因为在索引中,不存在的字段和null字段的存储方式是一样的,查询必须遍历每一个文档检查这个值是否真的为null还是根本不存在。
    $ne:可以使用索引,但并不是很高效。因为必须遍历整个索引条目才能找到结果的文档。
    $not:能够使用索引,但通常不知道如何使用索引,从而退化成全表扫描。
    $or:能够使用索引,但是$or 查询会将 or 的条件拆分成多个独立的查询,然后再将结果合并在一起。这是很低效的,不建议用。建议用 $in 取代 $or 。

    设计多键索引的时候要记得,要把基数大的字段放在索引的前面,因为这样能更快缩小查询的范围。

    二、索引类型

    • 复合(组合)索引

    复合索引就是一个建立在多个字段上的索引。
    如果查询中有多个排序方向或者查询条件中有多个键,复合索引就非常有效。

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

    进行多键排序时,索引的方向尤为重要。尽量做到多键排序的方向和复合索引的方向是一致的,因为这能很大的避免在内存中进行排序的运算。
    tips相互反转(在每个方向上都乘以-1)的索引是等价的:{"age" : 1, "user name" : -1}适用的查询与{"age" : -1, "username" : 1}是完全一样的。

    复合索引具有双重功能,而且对不同的查询可以表现为不同的索引。如果有一个{"age" :1, "username" : 1}索引,"age"字段会被自动排序,就好像有一个{"age" : 1}索引一样。因此,这个复合索引可以当作{"age" : 1}索引一样使用。

    • 唯一索引

    唯一索引可以确保集合的每一个文档的指定键都有唯一值。我们熟悉的 "_id" 索引就是一个唯一索引(但它不能被除,而其他唯一索引是可以除的)。

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

    定义了唯一索引后,这个键就不允许插入重复的值了,否则会抛异常。
    tipsA 字段不存在 和 A 字段为 null 是互斥的!

    在已有的集合上创建唯一索引可能会报错,因为集合中可能已经有重复的值了。在极少数情况下,可能希望直接删除重复的值。创建索引时使用"dropDups"选项,如果遇到重复的值,第一个会被保留,之后的重复文档都会被删除。

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

    • 稀疏索引

    在有些情况下,你可能希望唯一索引只对包含相应键的文档生效。如果有一个可能存在也可能不存在的字段,但是当它存在时,它必须是唯一的,这时就可以将unique和sparse选项组合在一起使用,创建唯一稀疏索引。注意:MongoDB中的稀疏索引(sparse index)与关系型数据库中的稀疏索引是完全不同的概念。基本上来说,MongoDB中的稀疏索引只是不需要将每个文档都作为索引条目。并且,稀疏索引并不一定是唯一的。

    db.ensureIndex({"email" : 1}, {"unique" : true, "sparse" : true})

    当某个查询使用了稀疏索引,就不会返回不包含这个字段的文档。因为稀疏索引并没有把每个文档都作为索引条目。

    • 覆盖索引

    如果你的查询只需要查找索引中包含的字段,那就根本没必要获取实际的文档。当一个索引包含用户请求的所有字段,可以认为这个索引覆盖了本次查询。所以,尽可能使用投射筛选返回的字段,比如 {"_id":0,"age":1} 等,来实现覆盖索引。

    三、索引管理

    • 新建索引

    普通索引

    db.userInfo.ensureIndex({"name":1},{"name","MyIndex"})
    "1" 表示按照name进行升序,"-1" 表示按照name进行降序。
    默认的索引以 key1_1_key2_-1 这样的方式命名,可以手动指定索引的名字,如上。

    对象索引

    可以对整个对象建立索引,或者对对象的某个元素使用索引。

    db.users.ensureIndex({"loc" : 1})
    只有在进行与对象字段顺序完全匹配的子文档查询时(比如db.users.find({"loc" :{"ip" : "123.456.789.000", "city" : "Shelbyville", "state" :"NY"}}})),查询优化器才会使用"loc"上的索引。

    db.users.ensureIndex({"loc.city" : 1})
    有涉及到对象city的查询都会使用这个索引。

    数组索引

     对数组建立索引,实际上是对数组的每个元素建立一个索引条目。比如一个文档中的数组字段有20个元素,那么该文档就拥有了20个索引条目!所以对数组字段的索引建立要慎重。

    • 删除索引

    db.userInfo.dropIndexes("name_1")
    删除指定索引

    db.userInfo.dropIndexes()
    删除除了_id 以外的所有索引

    • 操作索引

    获取当前索引列表:db.userInfo.getIndexes()

    hint 暴力选择某种索引:db.userInfo.find({name:'zhangsan',birthday:'1989-3-2'}).hint({"name":1,"birthday":1})

    强制使用全表扫描:db.userInfo.find({"birthday" : {"$lt" :"1989-3-2"}}).hint({"$natural" : 1})

    索引分析函数explain:MongoDB 3.0前 和 MongoDB 3.0后存在很大的差异,这里只简单说明下,如果想详细了解的话,可以关注该作者的文章:

    MongoDB 3.0 前:db.driverLocation.find({"areaCode":"350203"}).explain()

    cursor:表扫描方式 (basicCursor:顺序查找)
    nscanned:浏览了多少文档
    n:最终返回了几个文档
    millis:总共耗时了多少毫秒
    scanAndOrder:是否必须在内存中对数据进行排序

    MongoDB 3.0 后:db.driverLocation.find({"areaCode":"350203"}).explain("executionStats")

    executionTimeMillis:该query的整体查询时间
    nReturned:查询返回的条目
    totalKeysExamined:索引扫描条目
    totalDocsExamined:文档扫描条目 

  • 相关阅读:
    MySQL内连接和外连接
    MySQL 重命名数据库
    linux查看文件大小
    Linux合并两个文件夹内容
    Linux压缩和解压命令
    深度学习反向求导
    深度学习网络压缩模型方法总结
    cuda培训素材
    cuda编程-卷积优化
    交叉熵代价函数(损失函数)及其求导推导
  • 原文地址:https://www.cnblogs.com/jmcui/p/8757299.html
Copyright © 2020-2023  润新知