天天看点

MongoDB · 特性分析 · 索引原理

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

比如上面的例子里,<code>person</code>集合里包含插入了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}

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

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

age

18

19

20

21

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

查询,比如查询年龄为18的所有人

更新/删除,将年龄为18的所有人的信息更新或删除,因为更新或删除时,需要根据条件先查询出所有符合条件的文档,所以本质上还是在优化查询

排序,将所有人的信息按年龄排序,如果没有索引,需要全表扫描文档,然后再对扫描的结果进行排序

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

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

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

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

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

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

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

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

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

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

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

0: 不开启profiling

1: 将处理时间超过某个阈值(默认100ms)的请求都记录到db下的system.profile集合 (类似于mysql、redis的slowlog)

2: 将所有的请求都记录到db下的system.profile集合(生产环境慎用)

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

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

根据某个/些字段查询,但没有建立索引

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

<a href="https://docs.mongodb.org/manual/core/indexes">mongodb索引介绍</a>

<a href="https://docs.mongodb.org/manual/reference/method/db.collection.createindex/">createindex命令</a>

<a href="https://yq.aliyun.com/articles/32434?spm=5176.100238.yqhn2.22.0cuwgh">mongodb sharded cluster</a>

<a href="https://docs.mongodb.org/v3.0/tutorial/create-a-unique-index/">唯一索引 (unique index)</a>

<a href="https://docs.mongodb.org/manual/core/index-ttl/">ttl索引</a>

<a href="https://docs.mongodb.org/manual/core/index-partial/">部分索引 (partial index)</a>

<a href="https://docs.mongodb.org/manual/core/index-sparse/">稀疏索引(sparse index)</a>

<a href="https://docs.mongodb.org/manual/tutorial/manage-the-database-profiler/">database profiling</a>