chapter5 索引

5.1 索引简介

    数据库索引与书籍的索引类似。有了索引就不需要翻整本书,数据库可以直接在索引中查找,在索引中找到条目以后,就可以直接跳到目标文档的位置,这能使查询速度提高几个数量级。不使用索引的查询称为全表扫描。

> for(i=0;i<200;i++){
...   db.users.insert(
...     {
...       "i":i,
...       "username":"user"+i,
...       "age":Math.floor(Math.random()*120),
...       "created":new Date()
...     }
...   );
... }
WriteResult({ "nInserted" : 1 })
> db.users.find({username:"user101"}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test.users",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "username" : {
                                "$eq" : "user101"
                        }
                },
                "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "user101"
                                }
                        },
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27017,
                "version" : "3.4.0",
                "gitVersion" : "f4240c60f005be757399042dc12f6addbc3170c1"
        },
        "ok" : 1
}
> db.users.find({username:"user101"}).limit(1).explain()

    索引可以根据给定的字段组织数据,让MongoDB能够非常快的找到目标文档。在username字段上创建一个索引:

>db.users.ensureIndex({"username":1})
> db.users.ensureIndex({"username":1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> db.users.find({"username":"user101"}).explain()

5.1.1 复合索引简介

    索引的值是按一定顺序排列的,只有在首先使用索引键进行排序时,索引才有用。下面的排序里"username"上的索引没什么作用:

> db.users.find().sort({"age":1,"username":1})

    为了优化这个排序,可能需要在"age"和"username"上建立索引。

> db.users.ensureIndex({"age":1,"username":1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}

    如果使用{"age":1,"username":1}建立索引,这个索引大致会是这个样子:


    "age"相同的条目按照"username"升序排列

  • db.users.find({"age":21}).sort({"username":-1})

    这是一个点查询(point query),用于查找单个值。由于索引中的第二个字段,查询结果已经是有序的了:MongoDB可以从{"age":21}匹配的最后一个索引开始,逆序依次遍历索引:


  • db.users.find({"age":{"$gte":21,"$lte":30}})

    这是一个多值查询(multi-value query):


  • db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1})

    使用这个索引得到的结果集中"usernaem"是无序的。

> db.users.find({"age":{"$gte":21,"$lte":30}}).
... sort({"username":1}).
... explain() // 通过explain()查看MongoDB对db.users.find(...)的默认行为

    可以通过hint来强制MongoDB使用某个特定的索引,再次执行这个查询,但是这次使用{"username":1,"age":1}作为索引。这个查询扫描的文档比较多,但是不需要在内存中对数据排序:

> db.users.find({"age":{"$gte":21,"$lte":30}}).
... sort({"username":1}).
... hint({"username":1,"age":1}).
... explain()

5.1.2 使用复合索引

1.选择键的方向

{"age":1,"username":-1}


2.使用覆盖索引(covered index)

    当一个索引包含用户请求的所有字段,可以认为这个索引覆盖了本次查询。如果在覆盖索引上执行explain(),"indexOnly"字段的值要为true。

3.隐式索引

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

5.1.3 $操作符如何使用索引

1.低效率的操作符

    有一些查询完全无法使用索引,比如"$where"查询和检查一个键是否存在的查询({"key":{"$exists":true}})。

    通常来说,取反的效率是比较低的。大多数使用"$not"的查询都会退化为进行全表扫描。"$nin"就总是进行全表扫描。

2.范围


    这个查询会直接定位到"age"为47的索引条目,然后在其中搜索用户名介于"user5"和"user8"的条目。

    反过来,假如使用{"username":1,"age":1}索引,这样就改变了查询计划,查询必须先找到介于"user5"和"user8"之间的所有用户,然后再从中挑选"age"等于47的用户。


3.OR查询
>db.foo.find({"$or":[{"x":123},{"y":456}]).explain()
    "$or"实际上是执行两次查询然后将结果集合并。可以看到,这次的explain()输出结果由两次独立的查询组成。通常来说,执行两次查询再将结果合并的效率不如单词查询高,因此,应该尽可能使用"$in"而不是"$or"。

5.1.4 索引对象和数组

1.索引嵌套文档


    需要在"loc"的某一个子字段(比如"loc.city")上建立索引,以便提高这个字段的查询速度:

>db.users.ensureIndex({"loc.city":1})

    对嵌套文档本身("loc")建立索引,与对嵌套文档的某个字段("loc.city")建立索引是不同的。对整个子文档建立索引,只会提高整个子文档的查询速度。

2.索引数组

    在博客文章集合中嵌套的"comments"数组的"date"键上建立索引:

>db.blog.ensureIndex({"comments.date":1})

    对数组建立索引,实际上是对数组中的每个元素建立索引,而不是对数组本身建立索引。

    一个索引中的数组字段最多只能有一个。假如有一个{"x":1,"y":1}上的索引:


    如果MongoDB要为上面的最后一个例子创建索引,它必须要创建这么多索引条目:


3.多键索引

    对于某个索引的键,如果这个键在某个文档是一个数组,那么这个索引就会被标记为多键索引(multikey index)。可以从explain()的输出中看到一个索引是否为多键索引:如果使用了多键索引,"isMultikey"字段的值会是true。

5.1.5 索引基数

    基数(cardinality)就是集合中某个字段拥有不同值的数量。通常,一个字段的基数越高,这个键上的索引就越有用。这是因为索引能够迅速将搜索范围缩小到一个比较小的结果集。对于低基数的字段,索引通常无法排除掉大量可能的匹配。

5.2 使用explain()和hint()

    expalin()能够提供大量与查询相关的信息。对于速度比较慢的查询来说,这是最重要的诊断工具之一。最常见的explain()输出有两种类型:使用索引的查询和没有使用索引的查询。

    对于使用了复合索引的查询,最简单情况下的explain()输出如下所示:


  • "cursor":"BtreeCursor age_1_username_1":BtreeCursor表示本次查询使用了索引,具体来说,是使用了"age"和"username"上的索引{"age":1,"username":1}。
  • "isMultiKey":false:用于说明本次查询是否使用了多键索引
  • "n":8332:本次查询返回的文档数量
  • "nscannedObjects":8332:如果有使用索引,那么这个数字就是查找过的索引条目数量。如果是全表扫描,那么这个数字就表示检查过的文档数量。
  • "scanAndOrder":false:是否在内存中对结果集进行了排序
  • "indexOnly":false:是否只使用索引就能完成此次查询
  • "nYields":0:为了让写入请求能够顺利执行,本次查询暂停的次数。
  • "millis":91:数据库执行本次查询所耗费的毫秒数。
  • "indexBounds":{...}:索引的遍历范围

    假如有一个{"username":1,"age":1}上的一个索引和一个{"age":1,"username":1}上的索引。同时查询"username"和"age"时:


    可以使用hint()强制MongoDB使用特定的索引。例如,如果希望MongoDB在上个例子的查询中使用{"username":1,"age":1}索引,可以:

>db.c.find({"age":14,"username":/.*/}).hint({"username":1,"age":1})

5.3 何时不应该使用索引


5.4 索引类型

5.4.1 唯一索引

     唯一索引可以确保集合的每一个文档的指定键都有唯一值。

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

1.复合唯一索引

    创建复合唯一索引时,单个键的值可以相同,但所有键的组合值必须是唯一的。


    如果试图再次插入这三个文档中的任意一个,都会导致键重复异常。

2.去除重复

    在已有的集合上创建唯一索引可能会失败:


    创建索引时使用"dropDups"选项,如果遇到重复的值,第一个会被保留,之后的重复文档都会被删除。


5.4.2 稀疏索引

    使用sparse选项就可以创建稀疏索引。例如,如果有一个可选的email地址字段,但是,如果如果提供了这个字段,那么它的值必须是唯一的:

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

    稀疏索引不必是唯一的。只要去掉unique选项,就可以创建一个非唯一的稀疏索引。




    如果在"x"上创建一个稀疏索引,"_id"为0的文档就不会包含在索引中。


5.5 索引管理

    所有的数据库索引信息都存储在system.indexes集合中。只能通过ensureIndex或者dropIndexes对其进行操作。

可以运行db.collectionName.getIndexes()查看给定集合上的所有索引信息。

标识索引:


修改索引:








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值