为什么需要索引?

当你抱怨MongoDB集合查询效率低的时候,可能你就需要考虑使用索引了,为了方便后续介绍,先科普下MongoDB里的索引机制(同样适用于其他的数据库比如mysql)。

mongo-9552:PRIMARY> db.person.find()
{ "_id" : ObjectId("571b5da31b0d530a03b3ce82"), "name" : "jack", "age" : 19 }
{ "_id" : ObjectId("571b5dae1b0d530a03b3ce83"), "name" : "rose", "age" : 20 }
{ "_id" : ObjectId("571b5db81b0d530a03b3ce84"), "name" : "jack", "age" : 18 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce85"), "name" : "tony", "age" : 21 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce86"), "name" : "adam", "age" : 18 }

当你往某各个集合插入多个文档后,每个文档在经过底层的存储引擎持久化后,会有一个位置信息,通过这个位置信息,就能从存储引擎里读出该文档。比如mmapv1引擎里,位置信息是『文件id + 文件内offset 』, 在wiredtiger存储引擎(一个KV存储引擎)里,位置信息是wiredtiger在存储文档时生成的一个key,通过这个key能访问到对应的文档;为方便介绍,统一用pos(position的缩写)来代表位置信息。

比如上面的例子里,person集合里包含插入了4个文档,假设其存储后位置信息如下(为方便描述,文档省去_id字段)

位置信息 文档
pos1 {“name” : “jack”, “age” : 19 }
pos2 {“name” : “rose”, “age” : 20 }
pos3 {“name” : “jack”, “age” : 18 }
pos4 {“name” : “tony”, “age” : 21}
pos5 {“name” : “adam”, “age” : 18}

假设现在有个查询 db.person.find( {age: 18} ), 查询所有年龄为18岁的人,这时需要遍历所有的文档(『全表扫描』),根据位置信息读出文档,对比age字段是否为18。当然如果只有4个文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。

如果想加速 db.person.find( {age: 18} ),就可以考虑对person表的age字段建立索引。

db.person.createIndex( {age: 1} )  // 按age字段创建升序索引

建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,索引结构类似如下,索引通常采用类似btree的结构持久化存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。

age 位置信息
18 pos3
18 pos5
19 pos1
20 pos2
21 pos4

简单的说,索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。有了索引,至少能优化如下场景的效率:

  • 查询,比如查询年龄为18的所有人
  • 更新/删除,将年龄为18的所有人的信息更新或删除,因为更新或删除时,需要根据条件先查询出所有符合条件的文档,所以本质上还是在优化查询
  • 排序,将所有人的信息按年龄排序,如果没有索引,需要全表扫描文档,然后再对扫描的结果进行排序

众所周知,MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),_id是文档唯一的标识,为了保证能根据文档id快递查询文档,MongoDB默认会为集合创建_id字段的索引。

mongo-9552:PRIMARY> db.person.getIndexes() // 查询集合的索引信息
[{"ns" : "test.person",  // 集合名"v" : 1,               // 索引版本"key" : {              // 索引的字段及排序方向"_id" : 1           // 根据_id字段升序索引},"name" : "_id_"        // 索引的名称}
]

MongoDB索引类型

MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。

单字段索引 (Single Field Index)

    db.person.createIndex( {age: 1} )

上述语句针对age创建了单字段索引,其能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。

{age: 1} 代表升序索引,也可以通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。

复合索引 (Compound Index)

复合索引是Single Field Index的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推,如下针对age, name这2个字段创建一个复合索引。

    db.person.createIndex( {age: 1, name: 1} )

上述索引对应的数据组织类似下表,与{age: 1}索引不同的时,当age字段相同时,在根据name字段进行排序,所以pos5对应的文档排在pos3之前。

age,name 位置信息
18,adam pos5
18,jack pos3
19,jack pos1
20,rose pos2
21,tony pos4

复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询,比如db.person.find( {age: 18, name: "jack"} ),也能满足所以能匹配符合索引前缀的查询,这里{age: 1}即为{age: 1, name: 1}的前缀,所以类似db.person.find( {age: 18} )的查询也能通过该索引来加速;但db.person.find( {name: "jack"} )则无法使用该复合索引。如果经常需要根据『name字段』以及『name和age字段组合』来查询,则应该创建如下的复合索引

db.person.createIndex( {name: 1, age: 1} )

除了查询的需求能够影响索引的顺序,字段的值分布也是一个重要的考量因素,即使person集合所有的查询都是『name和age字段组合』(指定特定的name和age),字段的顺序也是有影响的。

age字段的取值很有限,即拥有相同age字段的文档会有很多;而name字段的取值则丰富很多,拥有相同name字段的文档很少;显然先按name字段查找,再在相同name的文档里查找age字段更为高效。

多key索引 (Multikey Index)

当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个habbit字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用habbit字段的多key索引。

{"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
db.person.createIndex( {habbit: 1} )  // 自动创建多key索引
db.person.find( {habbit: "football"} )

其他类型索引

哈希索引(Hashed Index)是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。

地理位置索引(Geospatial Index)能很好的解决O2O的应用场景,比如『查找附近的美食』、『查找某个区域内的车站』等。

文本索引(Text Index)能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。

索引额外属性

MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性。

  • 唯一索引 (unique index):保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引
  • TTL索引:可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)
  • 部分索引 (partial index): 只针对符合某个特定条件的文档建立索引,3.2版本才支持该特性
  • 稀疏索引(sparse index): 只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况

索引优化

db profiling

MongoDB支持对DB的请求进行profiling,目前支持3种级别的profiling。

  • 0: 不开启profiling
  • 1: 将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合 (类似于mysql、redis的slowlog)
  • 2: 将所有的请求都记录到DB下的system.profile集合(生产环境慎用)

通常,生产环境建议使用1级别的profiling,并根据自身需求配置合理的阈值,用于监测慢请求的情况,并及时的做索引优化。

如果能在集合创建的时候就能『根据业务查询需求决定应该创建哪些索引』,当然是最佳的选择;但由于业务需求多变,要根据实际情况不断的进行优化。索引并不是越多越好,集合的索引太多,会影响写入、更新的性能,每次写入都需要更新所有索引的数据;所以你system.profile里的慢请求可能是索引建立的不够导致,也可能是索引过多导致。

查询计划

索引已经建立了,但查询还是很慢怎么破?这时就得深入的分析下索引的使用情况了,可通过查看下详细的查询计划来决定如何优化。通过执行计划可以看出如下问题

  1. 根据某个/些字段查询,但没有建立索引
  2. 根据某个/些字段查询,但建立了多个索引,执行查询时没有使用预期的索引。

建立索引前,db.person.find( {age: 18} )必须执行COLLSCAN,即全表扫描。

mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.person","indexFilterSet" : false,"parsedQuery" : {"age" : {"$eq" : 18}},"winningPlan" : {"stage" : "COLLSCAN","filter" : {"age" : {"$eq" : 18}},"direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "localhost","port" : 9552,"version" : "3.2.3","gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"},"ok" : 1
}

建立索引后,通过查询计划可以看出,先进行[IXSCAN]((https://docs.mongodb.org/manual/reference/explain-results/#queryplanner)(从索引中查找),然后FETCH,读取出满足条件的文档。

mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.person","indexFilterSet" : false,"parsedQuery" : {"age" : {"$eq" : 18}},"winningPlan" : {"stage" : "FETCH","inputStage" : {"stage" : "IXSCAN","keyPattern" : {"age" : 1},"indexName" : "age_1","isMultiKey" : false,"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 1,"direction" : "forward","indexBounds" : {"age" : ["[18.0, 18.0]"]}}},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "localhost","port" : 9552,"version" : "3.2.3","gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"},"ok" : 1
}

参考资料

  • MongoDB索引介绍
  • createIndex命令
  • MongoDB Sharded Cluster
  • 唯一索引 (unique index)
  • TTL索引
  • 部分索引 (partial index)
  • 稀疏索引(sparse index)
  • database profiling

原文链接:http://www.mongoing.com/archives/2797

MongoDB索引原理相关推荐

  1. MongoDB · 引擎特性 · MongoDB索引原理

    MongoDB · 引擎特性 · MongoDB索引原理 数据库内核月报 原文链接 http://mysql.taobao.org/monthly/2018/09/06/ 为什么需要索引? 当你抱怨M ...

  2. MongoDB索引原理及实践

    背景 数据库的演进 随着计算机的发展,越来越多的数据需要被处理,数据库是为处理数据而产生.从概念上来说,数据库是指以一定的方式存储到一起,能为多个用户共享,具有更可能小的冗余,与应用程序彼此独立的数据 ...

  3. MongoDB索引原理和具体使用

    1. MongoDB 索引是用来干嘛? 索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录. 这种扫描全集合的查询效率是非常 ...

  4. MongoDb 索引原理

    (一)索引基本介绍        索引是提高查询查询效率最有效的手段.索引是一种特殊的数据结构,索引以易于遍历的形式存储了数据的部分内容(Mongodb和Mysql使用B+树)(如:一个特定的字段或一 ...

  5. 学习 | MongoDB 索引和排序

    小小又开始学习了,这次学习的内容是索引和排序. 索引 先给users集合插入两条记录,然后用users集合来进行索引管理的演示: > user1={"name":" ...

  6. MongoDB索引优化

    MongoDB 索引优化 1. 一图看懂索引原理 2. 查看执行计划 3. 如何建索引 3. 索引的优化 4. 索引的选择机制 5. 优化实践 country_themes 优化 wallpapers ...

  7. 【大数据存储技术】第7章 MongoDB 的原理和使用

    文章目录 第7章 MongoDB 的原理和使用 7.1 概述 7.2 MongoDB 技术原理 7.2.1 文档和集合 7.2.2 分片机制和集群架构 7.2.3 CouchDB 简介 7.3 安装配 ...

  8. Mysql索引原理剖析与优化策略

    Mysql索引原理剖析与优化策略 1.索引的本质  在⽣产环境中,随着数据量不断的增⻓,SQL执⾏速度会越来越慢,常⻅的⼿段就是通过索引来提升查询速度,那么究竟为什么要添加索引?应该如何正确添加索引? ...

  9. M14-MongoDB索引原理及使用

    存储引擎 network-Query Pan-Storage KV Interface-WiredTiger 核心数据结构B-Tree MongoDB数据结构组织 索引原理总结 MongoDB索引类型 ...

  10. MongoDB分布式原理以及read-preference和readConcern解决读写一致性问题

    MongoDB词汇表: https://docs.mongodb.com/manual/reference/glossary/#term-replica-set MongoDB分布式原理 primar ...

最新文章

  1. python实现字典遍历稳定有序使用collection包OrderedDict
  2. java 完全匹配,Java 正则表达式匹配模式(贪婪型、勉强型、占有型)
  3. Qt工作笔记-WebEngineView调用web站点中的JS脚本(含Vue Cli脚本)
  4. ie浏览器网页版进入_荟萃浏览器v2.10.2清爽版 网页秒开/装机必备
  5. SpringBoot之项目启动
  6. python 清空文件夹_python读写文件
  7. druid 配置WebStatFilter 网络统计以及监控
  8. “腾讯基因”讨论:为什么我常说做to C的人很难去做to B?
  9. 第五周-第08章节-Python3.5-内置模块详解之shutil模块
  10. python游戏dnf_招募:基于python的召唤师全时段全技能(含均值AI)计算器全程测试...
  11. NSUOJ2888最小唯一表示前缀(偷懒的xzj)
  12. 电商商品3d展示---插件spritespin
  13. 富文本样式文字图片处理
  14. 西瓜书(周志华)课后习题答案
  15. 牛客网在线编程专题《剑指offer-面试题15》链表中倒数第k个节点
  16. 智能晾衣杆_晾衣绳社交书签–仅CSS的社交书签教程
  17. 重建分区表,修复无法格式化的U盘
  18. docker的几种镜像仓库,你用过几个?
  19. 数学与计算机科学国际研讨会怎么样,科学网—阿狗迎新──2018年算法数学国际学术会议预告 - 王东明的博文...
  20. 启动http监听失败、添加URL保留项失败,错误6句柄无效

热门文章

  1. 傅里叶变换就是这么简单?
  2. 如何设置局域网内的固定IP地址?
  3. Github访问和下载慢的解决与提升方案
  4. brandon公司_开发人员聚焦:布兰登·里德(Brandon Reid)
  5. 【JAXP】Dom方式解析XML文件
  6. 前端遇到GET https://XXXX net::ERR_HTTP2_PROTOCOL_ERROR 200问题的解决办法
  7. oracle 整理表 碎片,Oracle数据库表空间碎片的查询和整理方法
  8. 虚拟化安全防护系统部署在安全服务器上,虚拟化安全及解决方案
  9. 【生活中的逻辑谬误】以先后论因果和简化推理
  10. ESP32-C3入门教程 IoT篇⑤——阿里云 物联网平台 EspAliYun RGB LED 实战之设备生产流程