# mongodb 索引


<!--more-->


## 1.索引简介

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

## 2.索引类型

### 2.1 单键索引 single field

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

对于单个字段索引，索引键的排序顺序无关紧要，因为MongoDB可以在任一方向读取索引

**特殊的单键索引过期索引TTL（TimeToLive）**:

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

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

### 2.2复合索引compound index

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

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

> 1 为指定按升序创建索引，如果你想按降序来创建索引指定为 -1 即可。

### 2.3 多键索引multikey indexes

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

### 2.4 地理空间索引 geospatial index

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

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

```
db.company.insert( 
	{ 
		loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] }, 
		name: "大望路地铁", 
		category : "Parks" } )
		
db.company.ensureIndex( { loc : "2dsphere" } ) 
参数不是1或-1，为2dsphere 或者 2d。还可以建立组合索引。 
db.company.find({ 
	"loc" : { 
		"$geoWithin" : { 
			"$center":[[116.482451,39.914176],0.05] 
			} 
	} 
})
```

### 2.5 全文索引

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

```
db.集合.createIndex({"字段": "text"}) 
db.集合.find({"$text": {"$search": "coffee"}})
```

### 2.6 hash索引

针对属性的哈希值进行索引查询，当要使用Hashed index时，MongoDB能够自动的计算hash值，无需程序计算hash值。注：hash index仅支持**等于查询**，不支持范围查询。

`db.集合.createIndex({"字段": "hashed"})`



## 3. 索引和explain分析

### 3.1 索引管理

- 创建索引并在后台运行

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

- 获取针对某个集合的索引

  ```
  db.COLLECTION_NAME.getIndexes()
  ```

- 索引的大小

  ```
  db.COLLECTION_NAME.totalIndexSize()
  ```

- 索引的重建

  ```
  db.COLLECTION_NAME.reIndex()
  ```

- 索引的删除

  ```
  db.COLLECTION_NAME.dropIndex("INDEX-NAME") 
  db.COLLECTION_NAME.dropIndexes() 
  注意: _id 对应的索引是删除不了的
  ```

### 3.2 explain分析

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

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

- **queryPlanner****：queryPlanner是默认参数，具体执行计划信息参考下面的表格。

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

- **allPlansExecution**:allPlansExecution用来获取所有执行计划，结果参数基本与上文相同。queryPlanner参数和executionStats的拼接

**queryPlanner默认参数**: ![img_4](https://img.zhaojq.top/20260727091816583.png "img_4")

**executionStats参数**: ![img_5](https://img.zhaojq.top/20260727091816803.png "img_5")

**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)



## 4. 慢查询分析

- 开启内置的查询分析器,记录读写操作效率

  db.setProfifilingLevel(n,m),n的取值可选0,1,2

  - 0表示不记录

  - 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值

  - 2表示记录所有的读写操作

- 查询监控结果

  db.system.profile.find().sort({millis:-1}).limit(3)

- 分析慢速查询

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

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

## 5. 索引底层实现原理分析

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

B-树是一种自平衡的搜索树，形式很简单：![img_6](https://img.zhaojq.top/20260727091817042.png "img_6")

> B-树的特点:
>
> (1)多路非二叉树
>
> (2)每个节点既保存数据又保存索引
>
> (3)搜索时相当于二分查找

B+树是B-树的变种: ![img_7](https://img.zhaojq.top/20260727091817307.png "img_7")

> B+ 树特点
>
> （1）多路非二叉
>
> （2）只有叶子节点保存数据
>
> （3）搜索时也相当于二分查找
>
> （4）增加了相邻节点指针

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

- B+树相邻接点的指针可以大大增加区间访问性，可使用在范围查询等，而B-树每个节点key和data在一起适合随机读写，而区间查找效率很差。
- B+树更适合外部存储，也就是磁盘存储，使用B-结构的话，每次磁盘预读中的很多数据是用不上的数据。因此，它没能利用好磁盘预读的提供的数据。由于节点内无data域，每个节点能索引的范围更大更精确。
- 注意这个区别相当重要，是基于以上两点的，B-树每个节点即保存数据又保存索引,树的深度小，所以磁盘IO的次数很少，B+树只有叶子节点保存，较B树而言深度大磁盘IO多，但是区间访问比较好。


