MongoDB索引Index

什么是索引

  索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。默认情况下Mongo在一个集合(collection)创建时,自动地对集合的_id创建了唯一索引。

索引类型

单键索引 (Single Field)

MongoDB支持所有数据类型中的单个字段索引,并且可以在文档的任何字段上定义。
对于单个字段索引,索引键的排序顺序无关紧要,因为MongoDB可以在任一方向读取索引。

单个例上创建索引:

db.集合名.createIndex({"字段名":排序方式})

特殊的单键索引过期索引 TTL ( Time To Live)

TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除,目前TTL索引只能在单字段上建立,并且字段类型必须是日期类型

db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒数})

复合索引(Compound Index)

  通常我们需要在多个字段的基础上搜索表/集合,这是非常频繁的。 如果是这种情况,我们可能会考虑在MongoDB中制作复合索引。 复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。

  制作复合索引时要注意的重要事项包括:字段顺序与索引方向。

db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )

多键索引(Multikey indexes)

  针对属性包含数组数据的情况,MongoDB支持针对数组中每一个element创建索引,Multikeyindexes支持strings,numbers和nested documents

地理空间索引(Geospatial Index)

针对地理空间坐标数据创建索引。

2dsphere索引,用于存储和查找球面上的点

2d索引,用于存储和查找平面上的点

db.company.insert(
   {
     loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] },
     name: "大望路地铁",
     category : "Parks"
   }
)

db.company.insert(
   {
     loc : { type: "Point", coordinates: [ 116.492451, 39.934176 ] },
     name: "test1",
     category : "Parks"
   }
)

db.company.insert(
   {
     loc : { type: "Point", coordinates: [ 116.462451, 39.954176 ] },
     name: "test2",
     category : "Parks"
   }
)


db.company.insert(
   {
     loc : { type: "Point", coordinates: [ 116.562451, 38.954176 ] },
     name: "test3",
     category : "Parks"
   }
)

db.company.insert(
   {
     loc : { type: "Point", coordinates: [ 117.562451, 37.954176 ] },
     name: "test4",
     category : "Parks"
   }
)

db.company.find({})

//参数不是1或-1,为2dsphere 或者 2d。还可以建立组合索引。
db.company.ensureIndex( { loc : "2dsphere" } )
db.company.getIndexes()

//查找坐标范围内五公里的位置记录
db.company.find({
    "loc" : { 
        "$geoWithin" : {
          "$center":[[116.482451,39.914176],0.05]
        }
    }
})

//查找当前位置最近的两条位置记录
db.company.aggregate([
    {
        $geoNear: {
          near: {
              type: "Point",
              coordinates: [116.482451,39.914176]
          },
          key: "loc",
          distanceField: "dist.calculated"
        }
        
    },    
    {$limit: 2}
])

在这里插入图片描述

在这里插入图片描述

2D位置索引

参考此篇文章
https://www.cnblogs.com/xubao/p/12283694.html

/** 2d 测试  */
db.places.drop()
db.places.insert({"name": "Temple1","tile": [32, 22]})
db.places.insert({"name": "Temple2","tile": [30, 22]})
db.places.insert({"name": "Temple3","tile": [28, 21]})
db.places.insert({"name": "Temple4","tile": [34, 27]})
db.places.insert({"name": "Temple5","tile": [34, 26]})
db.places.insert({"name": "Temple6","tile": [39, 28]})

db.places.find({})
 
db.places.ensureIndex({"tile" : "2d"}, {"min" : -90, "max" : 90, "bits" : 20}) 
//在指定形状查询中,$box、$polygon、$center分别表示按矩形、五边形、圆形进行查询。
// 这里查找在这个矩形范围内的数据
db.places.find({"tile": {"$within": {"$box": [[0, 0], [30, 30]]}}})

全文索引

MongoDB提供了针对string内容的文本查询,Text Index支持任意属性值为string或string数组元素的索引查询。注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。

db.集合.createIndex({"字段": "text"})
db.集合.find({"$text": {"$search": "coffee"}})
/** 全文索引  倒排索引 */
db.textTextIndex.insert({id:1,name:"test1",description:"one world one dream in bj",city:"bj"})
db.textTextIndex.insert({id:2,name:"test2",description:"two world two dream in nj",city:"nj"})
db.textTextIndex.insert({id:3,name:"test3",description:"dj is not bj and nj",city:"dj"})
db.textTextIndex.insert({id:4,name:"test4",description:"dj is not bj and nj",city:"dj"})

// 在没有建立全文 
db.textTextIndex.find({"$text": {"$search": "two"}})

db.textTextIndex.find()
db.textTextIndex.createIndex({description:"text"});
db.textTextIndex.find({"$text": {"$search": "two"}})
//后台创建索引,如果创建索引需要花费很久时间可以使用此选项
db.textTextIndex.createIndex({name:1},{background:true})
db.textTextIndex.getIndexes()
db.textTextIndex.totalIndexSize()

索引管理

索引的查看

说明:
返回一个集合中的所有索引的数组。
语法:

db.collection.getIndexes()

提示:该语法命令运行要求是MongoDB 3.0+
【示例】
查看comment集合中所有的索引情况

db.comment.getIndexes()

在这里插入图片描述
结果中显示的是默认_id 索引。
默认_id 索引:
MongoDB在创建集合的过程中,在_id 字段上创建一个唯一的索引,默认名字为_id_,该索引可防止客户端插入两个具有相同值的文档,您不能在_id字段上删除此索引。
注意:该索引是唯一索引,因此值不能重复,即_id 值不能重复的。在分片集群中,通常使用_id 作为片键。

索引的创建

说明:
在集合上创建索引。
语法:

db.collection.createIndex(keys, options)

参数:

ParameterTypeDescription
keysdocument包含字段和值对的文档,其中字段是索引键,值描述该字段的索引类型。对于字段上的升序索引,请指定值1;对于降序索引,请指定值-1。比如: {字段:1或-1} ,其中1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。另外,MongoDB支持几种不同的索引类型,包括文本、地理空间和哈希索引。
optionsdocument可选。包含一组控制索引创建的选项的文档。有关详细信息,请参见选项详情列表。

options(更多选项)列表:

ParameterTypeDescription
backgroundBoolean建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加"background" 可选参数。 “background” 默认值为false。
uniqueBoolean建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
namestring索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
dropDupsBoolean3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为false.
sparseBoolean对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSecondsinteger指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
vindex version索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weightsdocument索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_languagestring对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_overridestring对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为language.

提示:
注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex() ,之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。

【示例】
(1)单字段索引示例:对userid 字段建立索引:

db.comment.createIndex({userid:1})

(2)复合索引:对userid 和nickname 同时建立复合(Compound)索引:

db.comment.createIndex({userid:1,nickname:-1})

(3) 创建索引并在后台运行

db.COLLECTION_NAME.createIndex({"字段":排序方式}, {background: true});

索引的移除

说明:可以移除指定的索引,或移除所有索引
一、指定索引的移除
语法:

db.collection.dropIndex(index)

参数:

ParameterTypeDescription
indexstring or document指定要删除的索引。可以通过索引名称或索引规范文档指定索引。若要删除文本索引,请指定索引名称。

【示例】
删除comment 集合中userid 字段上的升序索引:

db.comment.dropIndex({userid:1})

所有索引的移除
语法:

db.collection.dropIndexes()

提示: _id 的字段的索引是无法删除的,只能删除非_id 字段的索引。

索引的重建

db.COLLECTION_NAME.reIndex()

explain 分析

使用js循环 插入100万条数据 不使用索引字段 查询查看执行计划 ,然后给某个字段建立索引,使用索引字段作为查询条件 再查看执行计划进行分析

//切换到company表,然后插入100w条数据,这里建议通过服务器中的MongoDB客户进行插入,这样能减少插入的耗时
use company

for(var i=0; i <1000000;i++) {
    db.company.insert([{id:i,name:"test"+i,salary:(Math.random()*20000).toFixed(2)}])
}

explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。

  • queryPlanner:queryPlanner是默认参数,具体执行计划信息参考下面的表格。

  • executionStats:executionStats会返回执行计划的一些统计信息(有些版本中和allPlansExecution等同)。

  • allPlansExecution:allPlansExecution用来获取所有执行计划,结果参数基本与上文相同。

  1. 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的child stage如果此处是IXSCAN,表示进行的是index scanning。
winningPlan.keyPattern所扫描的index内容
winningPlan.indexNamewinning plan所选用的index。
winningPlan.isMultiKey是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true。
winningPlan.direction此query的查询顺序,此处是forward,如果用了.sort({字段:-1})将显示backward。
filter过滤条件
winningPlan.indexBoundswinningplan所扫描的索引范围,如果没有制定范围就是[MaxKey,MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取。
rejectedPlans被拒绝的执行计划的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述)
serverInfoMongoDB服务器信息
  1. executionStats参数
参数含义
executionSuccess是否执行成功
nReturned返回的文档数
executionTimeMillis执行耗时
totalKeysExamined索引扫描次数
totalDocsExamined文档扫描次数
executionStages这个分类下描述执行的状态
stage扫描方式,具体可选值与上文的相同
nReturned查询结果数量
executionTimeMillisEstimate检索document获得数据的时间
inputStage.executionTimeMillisEstimate该查询扫描文档 index所用时间
works工作单元数,一个查询会分解成小的工作单元
advanced优先返回的结果数
docsExamined文档检查数目,与totalDocsExamined一致。检查了总共的document个数,而从返回上面的nReturned数量
  1. executionStats返回逐层分析
    第一层,executionTimeMillis最为直观explain返回值是executionTimeMillis值,指的是这条语句的执行时间,这个值当然是希望越少越好。
    其中有3个executionTimeMillis,分别是:

    • executionStats.executionTimeMillis 该query的整体查询时间。

    • executionStats.executionStages.executionTimeMillisEstimate 该查询检索document获得数据的时间。

    • executionStats.executionStages.inputStage.executionTimeMillisEstimate 该查询扫描文档 index所用时间。

    第二层,index与document扫描数与查询返回条目数 这个主要讨论3个返回项 nReturnedtotalKeysExaminedtotalDocsExamined,分别代表该条查询返回的条目、索引扫描条目、文档扫描条目。 这些都是直观地影响到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)

  2. allPlansExecution参数

queryPlanner 参数和executionStats的拼接

下面举几个例子说明:

  1. 在自定义的id字段没有建立索引的情况下,进行执行分析
db.company.find({id:{$gt:233345}}).explain("executionStats");

在这里插入图片描述

然后创建id字段的索引,再执行分析

db.company.createIndex({id:1})
db.company.find({id:{$gt:233345}}).explain("executionStats");

在这里插入图片描述
2. 如果选择不返回某个字段,就会出现winningPlan.stage : "PROJECTION_DEFAULT"

db.company.find({id:{$gt:233345}},{name:0}).explain("executionStats");

在这里插入图片描述

慢查询分析

  • 开启内置的查询分析器,记录读写操作效率
    db.setProfilingLevel(n,m),n的取值可选0,1,2

    • 0表示不记录
    • 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值
    • 2表示记录所有的读写操作
  • 查询监控结果
    db.system.profile.find().sort({millis:-1}).limit(3)

  • 分析慢速查询
    应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等

  • 解读explain结果 确定是否缺少索引

案例:

  • 开启内置慢查询分析器,执行会返回之前的值
    db.setProfilingLevel(1,500)
    
    在这里插入图片描述
  • 进行多次查询
    db.company.find({name:{$gt:"test9909"}});
    db.company.find({name:{$gt:"test1"}});
    db.company.find({name:{$eq:"test264678"}});
    db.company.find({name:{$lt:"test222222"}});
    db.company.find({id:{$gt:233345}},{name:0});
    db.system.profile.find().sort({millis:1}).limit(3)
    
    在这里插入图片描述

MongoDB 索引底层实现原理分析

  MongoDB 是文档型的数据库,它使用BSON 格式保存数据,比关系型数据库存储更方便。比如之前关系型数据库中处理用户、订单等数据要建立对应的表,还要建立它们之间的关联关系。但是BSON就不一样了,我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单,通俗易懂。MySql是关系型数据库,数据的关联性是非常强的,区间访问是常见的一种情况,底层索引组织数据使用B+树,B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历。MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,单次查询从结构上来看要快于MySql。

B-树是一种自平衡的搜索树,形式很简单:
在这里插入图片描述
B-树的特点:
(1) 多路 非二叉树
(2) 每个节点 既保存数据 又保存索引
(3) 搜索时 相当于二分查找
B+树是B-树的变种
在这里插入图片描述
B+ 树的特点:
(1) 多路非二叉
(2) 只有叶子节点保存数据
(3) 搜索时 也相当于二分查找
(4) 增加了 相邻节点指针

  从上面我们可以看出最核心的区别主要有俩,一个是数据的保存位置,一个是相邻节点的指向。就是这俩造成了MongoDB和MySql的差别。

(1)B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和data 在一起 适合随机读写 ,而区间查找效率很差。

(2)B+树更适合外部存储,也就是磁盘存储,使用B-结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。由于节点内无 data 域,每个节点能索引的范围更大更精确。

(3)注意这个区别相当重要,是基于(1)(2)的,B-树每个节点即保存数据又保存索引 树的深度小,所以磁盘IO的次数很少,B+树只有叶子节点保存,较B树而言深度大磁盘IO多,但是区间访问比较好。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值