mongodb索引初探

基础理论知识

索引作用

索引主要用于在mongodb数据库中执行高效查找,在没有索引的情况下在mongodb中查找数据需要遍历整个collection(相当于mysql的表)的每条document(相当于mysql表中的一行)来匹配所要查询的文档,索引可以减少查询时必须检查的文档数目。

索引的介绍

mongodb的索引 B-tree的数据结构进行存储,这样易于遍历的形式存储收藏集数据集的一小部分。
索引存储一个特定的或者一组字段的值,按照该字段的值排序。
索引条目的排序支持有效的相等匹配和基于范围的查询操作。 另外,MongoDB可以使用索引中的顺序返回排序的结果。

索引的类别

单字段索引

1.mongodb针对每个collection的_id都会建立唯一索引(使用者不指定colection的_id则mongodb会自己生成且保证唯一,如果使用者指定了_id的值,就需要自己保证数值的唯一性),在分片集群中一定要保持_id的唯一性防止出错。
2.在单字段索引中索引键的排序顺序无关紧要,因为mongodb可以按照索引的正序和倒序进行排序

复合索引

1.复合索引的顺序
创建复合索引以支持对多个字段进行排序。
您可以在索引的所有键或子集上指定排序; 但是,排序键必须按照它们在索引中出现的顺序列出。 例如,索引键样式{a:1,b:1}可以支持{a:1,b:1}上的排序,但不能支持{b:1,a:1}上的排序。
为了使查询使用复合索引进行排序,cursor.sort()文档中所有键的指定排序方向必须与索引键模式匹配或与索引键模式的反方向匹配。 例如,索引键模式{a:1,b:-1}可以支持{a:1,b:-1}和{a:-1,b:1}上的排序,但不能支持{a:- 1,b:-1}或{a:1,b:1}。
2. 复合索引的前缀
如果排序键对应于索引键或索引前缀,则MongoDB可以使用索引对查询结果进行排序。 复合索引的前缀是在索引键模式的开头由一个或多个键组成的子集。
例如,在数据集合上创建一个复合索引:

db.data.createIndex( { a:1, b: 1, c: 1, d: 1 } )

以下都能匹配到该索引:

{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: 1, c: 1 }

Example Index Prefix
db.data.find().sort( { a: 1 } ) { a: 1 }
db.data.find().sort( { a: -1 } ) { a: 1 }
db.data.find().sort( { a: 1, b: 1 } ) { a: 1, b: 1 }
db.data.find().sort( { a: -1, b: -1 } ) { a: 1, b: 1 }
db.data.find().sort( { a: 1, b: 1, c: 1 } ) { a: 1, b: 1, c: 1 }
db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } ) { a: 1, b: 1 }
当通过索引来进行查询和排序的的时候,这些操作不需要在内存中对结果集在进行排序。

3.索引的排序和非前缀子集
索引可以支持对索引键模式的非前缀子集进行排序操作。 为此,查询必须在排序键之前的所有前缀键上包含相等条件。
例子:
集合有如下索引

{ a: 1, b: 1, c: 1, d: 1 }

以下操作可以使用索引来获取排序顺序:
Example Index Prefix
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } ) { a: 1 , b: 1, c: 1 }
db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } ) { a: 1, b: 1, c: 1 }
db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } ) { a: 1, b: 1 }

查询分析

可以使用

db.collection.find("条件").explain("executionStats")

来分析一条语句执行所使用的查询计划(是否有用到索引,用到了哪个索引)。
使用之后结果如下

{
   "queryPlanner" : {
         "plannerVersion" : 1,
         ...
         "winningPlan" : {
               "stage" : "FETCH",
               "inputStage" : {
                  "stage" : "IXSCAN",
                  "keyPattern" : {
                     "quantity" : 1
                  },
                  ...
               }
         },
         "rejectedPlans" : [ ]
   },
   "executionStats" : {
         "executionSuccess" : true,
         "nReturned" : 3,
         "executionTimeMillis" : 0,
         "totalKeysExamined" : 3,
         "totalDocsExamined" : 3,
         "executionStages" : {
            ...
         },
         ...
   },
   ...
}
涵盖的意思如下
queryPlanner.winningPlan.inputStage.stage显示IXSCAN以指示索引的使用。
executionStats.nReturned显示3,指示查询匹配并返回三个文档。
executionStats.totalKeysExamined显示3,表示MongoDB扫描了三个索引条目。 检查的键数与返回的文档数匹配,这意味着mongod仅需检查索引键即可返回结果。 mongod不必扫描所有文档,只需要将三个匹配的文档拉入内存即可。 这导致非常有效的查询。
executeStats.totalDocsExamined显示3指示MongoDB扫描了三个文档。

winningplan表示的是mongodb计划选择使用的索引(也可能不走索引,collscan就是指全表扫描)
executionStates表示用winningplan执行查询的实际统计信息。
nReturned:表示符合查询(find)条件的文档数。
executionTimeMillis:选择查询计划和执行查询所需要的总时间。
totalKeysExamined:扫描索引的条目数
totalDocsExamined :查询期间执行检查的文档数。(ps:注意此处返回的数字是我们检索的文档总数,结果有时还需再与查询条件进行对比筛选后再返回,如索引获取800条,但是根据条件我们还需要筛选一部分出去,最后获取到的与条件匹配可能只有100条,两方差值越小代表索引效率越高。)
更详细的说明可参考:https://docs.mongodb.com/manual/reference/explain-results/#explain.executionStats.nReturned
有关stage常见解释

COLLSCAN进行集合扫描
IXSCAN用于扫描索引键
FETCH用于检索文档
SHARD_MERGE用于合并分片的结果
SHARDING_FILTER用于从分片中筛选出孤立文档
SORT:表明在内存中进行了排序

实际场景分析

数据库获取数据的过程

从以上理论知识,可以得出mongodb数据库获取数据的过程:
1.检查索引,选择最优化的索引(所以索引不是越多越好,建立太多的索引也会影响查询的性能)
2.根据索引(如果没有索引就是全表查询)获取所有匹配上索引的数据(若有排序操作,且排序也匹配上了索引,则可以在这一步直接排序)
3.拿到索引获取的数据集,再根据其他的查找条件对数据集再一次的进行过滤(所以索引的分辨度很重要)
4.若没有根据索引排序,再将最终结果在内存中进行一次排序

谨慎掉坑

1.执行查询及排序,索引使用了排序的索引,虽然排序使用了索引,但是由于查找条件没有匹配上索引,所以导致了全表查询

{ b: 1, c: 1, d: 1 }
存在索引b,c,d,执行以下查询
Example	                                                         Index 
db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } )          	{  b: 1, c: 1,d:1 }

这种情况会显示匹配了索引,但是由于在寻找数据集的阶段没有匹配索引,所以依旧是全表查询,但是排序上会比全表扫描优化一些,因为走了索引。
这种情况的理想的索引是{ a: 1, b: 1,c:1 }

2.谨慎使用$or

在mongodb中$or语句必须保证他的每一个条件都是索引,不然就会导致全表扫描

db.inventory.find( { $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] } )

为了支持此查询不走全表扫描,你必须在quantity和price上分别建立索引

db.inventory.createIndex( { quantity: 1 } )
db.inventory.createIndex( { price: 1 } )

3.查询必须在排序键之前的所有前缀键上包含相等条件

针对数据 abc表,有四个字段,a,b,c,d
建立索引

db.abc.createIndex({c:1})
db.abc.createIndex({a:1,b:1,c:1})

最合理场景当然是匹配上索引a,b,c这样即完成了查找又完成了排序

语句索引执行过程
db.abc.find({a:{$gt:2},b:1}).sort({c:1}){c:1}排序匹配了索引,但是查找没有,所以先全表扫描用c排序,再使用a,b去回表比较筛选
db.abc.find({a:2,b:1}).sort({c:1}){a:1,b:1,c:1}使用索引abc直接去匹配,性能最优

参考文章:
https://docs.mongodb.com/manual/indexes/index.html
https://docs.mongodb.com/manual/tutorial/analyze-query-plan/
https://docs.mongodb.com/manual/reference/operator/query/or/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值