一、索引简介
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB 在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
1.1 概念
索引最常用的比喻就是书籍的目录,查询索引就像查询一本书的目录。本质上目录是将书中一小部分内容信息(比如题目)和内容的位置信息(页码)共同构成,而由于信息量小(只有题目),所以我们可以很快找到我们想要的信息片段,再根据页码找到相应的内容。同样索引也是只保留某个域的一部分信息(建立了索引的 field 的信息),以及对应的文档的位置信息。
假设我们有如下文档(每行的数据在 MongoDB 中是存在于一个 Document 当中)
姓名 | id | 部门 | city | score |
---|---|---|---|---|
张三 | 2 | 开发部 | 北京 | 90 |
李四 | 1 | 测试部 | 上海 | 70 |
王五 | 3 | 运维部 | 河北 | 60 |
1.2 索引的作用
假如我们想找 id 为 2 的 document (即张三的记录),如果没有索引,我们就需要扫描整个数据表,然后找出所有 id 为 2 的 document。当数据表中有大量 documents 的时候,这个查询时间就会很长(从磁盘上查找数据还涉及大量的 IO 操作)。
此时建立索引后会有什么变化呢?MongoDB 会将 id 数据拿出来建立索引数据,如下:
索引值 | 位置 |
---|---|
1 | 第二行 |
2 | 第一行 |
3 | 第三行 |
此时,即可根据索引值快速得到原始数据的具体位置,从而获取完整的原始数据。
1.3 索引的工作原理
这样我们就可以通过扫描这个小表找到 document 对应的位置。
查找过程示意图如下:
索引为什么这么快:
为什么这样速度会快呢?这主要有几方面的因素
- 索引数据通过 B 树来存储,从而使得搜索的时间复杂度为 O (logdN) 级别的 (d 是 B 树的度,通常 d 的值比较大,比如大于 100),比原先 O (N) 的复杂度大幅下降。这个差距是惊人的。
- 索引本身是在高速缓存当中,相比磁盘 IO 操作会有大幅的性能提升。(需要注意的是,有的时候数据量非常大的时候,索引数据也会非常大,当大到超出内存容量的时候,会导致部分索引数据存储在磁盘上,这会导致磁盘 IO 的开销大幅增加,从而影响性能,所以务必要保证有足够的内存能容下所有的索引数据)
当然,事物总有其两面性,在提升查询速度的同时,由于要建立索引,所以写入操作时就需要额外的添加索引的操作,这必然会影响写入的性能,所以当有大量写操作而读操作比较少的时候,且对读操作性能不需要考虑的时候,就不适合建立索引。当然,目前大多数互联网应用都是读操作远大于写操作,因此建立索引很多时候是非常划算和必要的操作。
二、索引的优化
2.1 执行计划
MongoDB 中的
explain()
函数可以帮助我们查看查询相关的信息,这有助于我们快速查找到搜索瓶颈进而解决它,我们接下来就看看explain()
的一些用法及其查询结果的含义。
2.1.1 基本用法
先来看一个基本用法:
db.zips.find({"pop":99999}).explain()
直接跟在 find()
函数后面,表示查看 find()
函数的执行计划,结果如下:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "zips-db.zips",
"indexFilterSet" : false,
"parsedQuery" : {
"pop" : {
"$eq" : 99999
}
},
"queryHash" : "891A44E4",
"planCacheKey" : "2D13A19E",
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"pop" : 1
},
"indexName" : "pop_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"pop" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : true,
"indexVersion" : 2,
"direction" :