什么是索引
索引是⼀种单独的、物理的对数据库表中⼀列或多列的值进⾏排序的⼀种存储结构,它是某个表中⼀列或若⼲列值的集合和相应的指向表中物理标识这些值的数据⻚的逻辑指针清单。索引的作⽤相当于图书的⽬录,可以根据⽬录中的⻚码快速找到所需的内容。索引⽬标是提⾼数据库的查询效率,没有索引的话,查询会进⾏全表扫描(scan every document in a collection),数据量⼤时严重降低了查询效率。默认情况下Mongo在⼀个集合(collection)创建时,⾃动地对集合的_id创建了唯⼀索引。
索引类型
单键索引 (Single Field)
MongoDB⽀持所有数据类型中的单个字段索引,并且可以在⽂档的任何字段上定义。
对于单个字段索引,索引键的排序顺序⽆关紧要,因为MongoDB可以在任⼀⽅向读取索引。
单个例上创建索引:
1 | db.集合名.createIndex({"字段名":排序⽅式}) |
特殊的单键索引 过期索引 TTL ( Time To Live)
TTL索引是MongoDB中⼀种特殊的索引, 可以⽀持⽂档在⼀定时间之后⾃动过期删除,⽬前TTL索引只能在单字
段上建⽴,并且字段类型必须是⽇期类型。
1 | db.集合名.createIndex({"⽇期字段":排序⽅式}, {expireAfterSeconds: 秒数}) |
多键索引(Multikey indexes)
针对属性包含数组数据的情况,MongoDB⽀持针对数组中每⼀个element创建索引,Multikey indexes⽀持 strings,numbers和nested documents
地理空间索引(Geospatial Index)
针对地理空间坐标数据创建索引。 2dsphere索引,⽤于存储和查找球⾯上的点 2d索引,⽤于存储和查找平⾯上的点
1 | db.company.insert( |
全⽂索引
MongoDB提供了针对string内容的⽂本查询,Text Index⽀持任意属性值为string或string数组元素的索引查询。 注意:⼀个集合仅⽀持最多⼀个Text Index,中⽂分词不理想 推荐ES。
1 | db.集合.createIndex({"字段": "text"}) |
哈希索引 Hashed Index
针对属性的哈希值进⾏索引查询,当要使⽤Hashed index时,MongoDB能够⾃动的计算hash值,⽆需程序计算 hash值。注:hash index仅⽀持等于查询,不⽀持范围查询。
1 | db.集合.createIndex({"字段": "hashed"}) |
索引和explain 分析
索引管理
创建索引并在后台运行
1 | db.COLLECTION_NAME.createIndex("字段"∶排序方式},{background∶true}); |
获取针对某个集合的索引
1 | db.COLLECTION_NAME.getIndexes() |
索引的大小
1 | db.COLLECTION_NAME.totalIndexSize() |
索引的重建
1 | db.COLLECTION_NAME.reIndex() |
索引的删除
1 | db.COLLECTION_NAME.dropIndex("INDEX-NAME") |
explain 分析
使⽤js循环 插⼊100万条数据 不使⽤索引字段 查询查看执⾏计划 ,然后给某个字段建⽴索引,使⽤索引字段作为查 询条件 再查看执⾏计划进⾏分析 explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。
- queryPlanner:queryPlanner是默认参数,具体执⾏计划信息参考下⾯的表格。
- executionStats:executionStats会返回执⾏计划的⼀些统计信息(有些版本中和allPlansExecution等同)。
- allPlansExecution:allPlansExecution⽤来获取所有执⾏计划,结果参数基本与上⽂相同
queryPlanner 默认参数
参数 | 含义 |
---|---|
plannerVersion | 查询计划版本 |
namespace | 要查询的集合(该值返回的是该query所查询的表)数据库.集合 |
indexFilterSet | 针对该query是否有indexFilter |
parsedQuery | 查询条件 |
winningPlan | 被选中的执⾏计划 |
winningPlan.stage | 被选中执⾏计划的stage(查询⽅式),常⻅的有:COLLSCAN/全表扫描:(应该知道就是CollectionScan,就是所谓的“集合扫描”,和mysql中table |
scan/heap | scan类似,这个就是所谓的性能最烂最⽆奈的由来)、IXSCAN/索引扫描:(是IndexScan,这就说明我们已经命中索引了)、FETCH/根据索引去检索⽂档、SHARD_MERGE/合并分⽚结果、IDHACK/针对_id进⾏查询等 |
winningPlan.inputStage | ⽤来描述⼦stage,并且为其⽗stage提供⽂档和索引关键字。 |
winningPlan.stage的childstage | 如果此处是IXSCAN,表示进⾏的是index scanning。 |
winningPlan.keyPattern | 所扫描的index内容 |
winningPlan.indexName | winning plan所选⽤的index。 |
winningPlan.isMultiKey | 是否是Multikey,此处返回是false,如果索引建⽴在array上,此处将是true。 |
winningPlan.direction | 此query的查询顺序,此处是forward,如果⽤了.sort({字段:-1})将显示backward。 |
filter | 过滤条件 |
winningPlan.indexBounds | winningplan所扫描的索引范围,如果没有制定范围就是[MaxKey, MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取。 |
rejectedPlans | 被拒绝的执⾏计划的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述) |
serverInfo | MongoDB服务器信息 |
executionStats参数
参数 | 含义 |
---|---|
executionSuccess | 是否执⾏成功 |
nReturned | 返回的⽂档数 |
executionTimeMillis | 执⾏耗时 |
totalKeysExamined | 索引扫描次数 |
totalDocsExamined | ⽂档扫描次数 |
executionStages | 这个分类下描述执⾏的状态 |
stage | 扫描⽅式,具体可选值与上⽂的相同 |
nReturned | 查询结果数量 |
executionTimeMillisEstimate | 检索document获得数据的时间 |
inputStage.executionTimeMillisEstimate | 该查询扫描⽂档 index所⽤时间 |
works | ⼯作单元数,⼀个查询会分解成⼩的⼯作单元 |
advanced | 优先返回的结果数 |
docsExamined | ⽂档检查数⽬,与totalDocsExamined⼀致。检查了总共的document 个数,⽽从返回上⾯的nReturned数量 |
executionStats返回逐层分析
第⼀层,executionTimeMillis
最为直观explain返回值是executionTimeMillis
值,指的是这条语句的执⾏时间,这个值当然是希望越少越好。
其中有3个executionTimeMillis
,分别是:
executionStats.executionTimeMillis
该query的整体查询时间。executionStats.executionStages.executionTimeMillisEstimate
该查询检索document获得数据的时间。executionStats.executionStages.inputStage.executionTimeMillisEstimate
该查询扫描⽂档 index所⽤时间。
第⼆层,index与document扫描数与查询返回条⽬数 这个主要讨论3个返回项 nReturned
、totalKeysExamined
、totalDocsExamined
,分别代表该条查询返回的条⽬、索引扫描条⽬、⽂档扫描条⽬。 这些都是直观地影响到executionTimeMillis
,我们需要扫描的越少速度越快。 对于⼀个查询,我们最理想的状态是: nReturned=totalKeysExamined=totalDocsExamined
第三层,stage状态分析 那么⼜是什么影响到了totalKeysExamined
和totalDocsExamined
?是stage的类型。类型列举如下:
- COLLSCAN: 全表扫描
- IXSCAN: 索引扫描
- FETCH:根据索引去检索指定document
- SHARD_MERGE:将各个分⽚返回数据进⾏merge
- SORT:表明在内存中进⾏了排序
- LIMIT:使⽤limit限制返回数
- SKIP:使⽤skip进⾏跳过
- IDHACK:针对_id进⾏查询
- SHARDING_FILTER:通过mongos对分⽚数据进⾏查询
- COUNT:利⽤db.coll.explain().count()之类进⾏count运算
- TEXT:使⽤全⽂索引进⾏查询时候的stage返回
- PROJECTION:限定返回字段时候stage的返回
对于普通查询,我希望看到stage的组合(查询的时候尽可能⽤上索引):
Fetch+IDHACK
Fetch+IXSCAN
Limit+(Fetch+IXSCAN)
PROJECTION+IXSCAN
SHARDING_FITER+IXSCAN
不希望看到包含如下的stage:
COLLSCAN(全表扫描)
SORT(使⽤sort但是⽆index)
COUNT 不使⽤index进⾏count)
allPlansExecution参数
1 | queryPlanner 参数和executionStats的拼接 |
MongoDB 索引底层实现原理分析
MongoDB 是⽂档型的数据库,它使⽤BSON 格式保存数据,⽐关系型数据库存储更⽅便。⽐如之前关系型数据库中处理⽤户、订单等数据要建⽴对应的表,还要建⽴它们之间的关联关系。但是BSON就不⼀样了,我们可以把⼀ 条数据和这条数据对应的数据都存⼊⼀个BSON对象中,这种形式更简单,通俗易懂。
MySql是关系型数据库,数据 的关联性是⾮常强的,区间访问是常⻅的⼀种情况,底层索引组织数据使⽤B+树,B+树由于数据全部存储在叶⼦ 节点,并且通过指针串在⼀起,这样就很容易的进⾏区间遍历甚⾄全部遍历。
MongoDB使⽤B-树,所有节点都有Data域,只要找到指定索引就可以进⾏访问,单次查询从结构上来看要快于MySql。
B- 树
B-树是⼀种⾃平衡的搜索树,形式很简单:
B-树的特点:
- 多路 ⾮⼆叉树
- 每个节点 既保存数据 ⼜保存索引
- 搜索时 相当于⼆分查找
B+ 树
B+树是B-树的变种
B+ 树的特点:
- 多路⾮⼆叉
- 只有叶⼦节点保存数据
- 搜索时 也相当于⼆分查找
- 增加了 相邻节点指针
从上⾯我们可以看出最核⼼的区别主要有俩,⼀个是数据的保存位置,⼀个是相邻节点的指向。就是这俩造成了
MongoDB和MySql的差别。
- B+树相邻接点的指针可以⼤⼤增加区间访问性,可使⽤在范围查询等,⽽B-树每个节点 key 和 data 在⼀起适合随机读写 ,⽽区间查找效率很差。
- B+树更适合外部存储,也就是磁盘存储,使⽤B-结构的话,每次磁盘预读中的很多数据是⽤不上的数据。因 此,它没能利⽤好磁盘预读的提供的数据。由于节点内⽆ data 域,每个节点能索引的范围更⼤更精确。
- 注意这个区别相当重要,是基于(1)(2)(3)的,B-树每个节点即保存数据⼜保存索引 树的深度⼤,所以磁盘IO的次数多,B+树只有叶⼦节点保存,较B树⽽⾔深度⼩,磁盘IO少,有利于区间访问。