一、说句闲话
一个月以前失业了,对于做了十几年程序的人来说。 本来算不上什么问题继续找合适的工作就是了。但是经历了一个月的找工作,才发现自己掉入了一个前所未有的行业衰退期。几乎所有大厂都在裁员,小厂可能直接死掉了。据说地铁人多少了,虽然我有几年没有坐地铁了。但是可以想像得到地铁上疏松的人,甚至有座。。
面试是很卷的, 首先面试机会就很少。面试问的问题又很尖锐。有些曾经了如指掌的问题,背不下来,就直接挂了。面试官甚至比自己年龄小十几岁,或许他并没有那么多的经验或者无法理解背下来意义不如理解思想来的有价值。但是在那一刻,人家是"官"。
有同样遭遇的朋友,私信聊聊吧~~
二、是这么问的
MongoDB 用过吧?用在什么场景呢?
我根据业务实际使用场景描述了使用的场景,以及当时选择mongo 的原因。接下来就问
MongoDB的索引有哪些? 是怎么实现的?
我的确有点懵, 因为我确实不知道具体有哪些,更不知道实现原理。没有背到这块八股文。就根据使用过程中的一些经验,比如唯一索引, 复合索引,进行了简单描述。得到的反馈是不好的,感觉有点看不起我了。
今天就来整理一下关于MongoDB 的索引吧。
三、MongoDB 索引
1、Mongo的索引必要性
首先mongo 是文档型的数据库。 在没有索引的情况下会和mysql 一样扫描全表,进行查询。因为mongo不是行存储的, 所以在文档较大, 深度较深的情况下, 同样没有索引, 同样数据量的情况下, mongo的效率是远低于mysql 的,mysql 在几十万数据时, 即使没有索引,也回比较快,但是mongo 就会很慢。 所以在mongo的使用过程中,必须使用合适的索引。
2、Mongo索引类型
MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。
下面分别举例。并且以如下数据进行说明
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 }
{ "_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 }
(1)单字段索引
db.person.createIndex( {age: -1} )
上述语句针对age创建了单字段索引,其能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。
{age: 1} 代表升序索引,也可以通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。
可以直接类比mysql 的单子段索引。 使用B-tree 实现索引的存储。
(2)复合索引
复合索引是Single Field Index的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推,如下针对age, name这2个字段创建一个复合索引。
db.person.createIndex( {age: 1, name: 1} )
上述索引对应的数据组织类似下表,与{age: 1}索引不同的时,当age字段相同时,在根据name字段进行排序
复合索引 在使用中,也是左匹配原则,如果只根据 name 进行查询 , 则该索引无效。
在实际使用过程中。 建立如下索引效率会更高
db.person.createIndex( { name: 1,age: 1} )
因为age 的区分度并不高, 假如年龄从1-120岁, 那么区分度也就120个值。 假如总数据量12000000万 那么,在 根据age 锁定范围之后, 依然有100000个文档需要根据name 扫描。 如果反过来, name 区分度比较大。 可以更快的锁定名字范围, 甚至一下就查到。
(3)多key索引
当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个habbit字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用habbit字段的多key索引。
{"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
db.person.createIndex( {habbit: 1} ) // 自动创建多key索引
db.person.find( {habbit: "football"} )
(4)其他索引
哈希索引(Hashed Index)是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
地理位置索引(Geospatial Index)能很好的解决O2O的应用场景,比如『查找附近的美食』、『查找某个区域内的车站』等。
文本索引(Text Index)能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。
四、索引优化
1、DB Profiler
MongoDB支持对DB的请求进行profiling,目前支持3种级别的profiling。
0: 不开启profiling
1: 将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合 (类似于mysql、redis的slowlog)
2: 将所有的请求都记录到DB下的system.profile集合(生产环境慎用)
通常,生产环境建议使用1级别的profiling,并根据自身需求配置合理的阈值,用于监测慢请求的情况,并及时的做索引优化。
如果能在集合创建的时候就能『根据业务查询需求决定应该创建哪些索引』,当然是最佳的选择;但由于业务需求多变,要根据实际情况不断的进行优化。索引并不是越多越好,集合的索引太多,会影响写入、更新的性能,每次写入都需要更新所有索引的数据;所以你system.profile里的慢请求可能是索引建立的不够导致,也可能是索引过多导致。
2、查询计划
mongo-9552:PRIMARY> db.person.find({age: 18}).explain()
可以得到执行计划, 类似于 mysql 的 explain sql
{
"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
}