MongoDB索引详解

一、索引

1、什么是索引

索引是特殊的数据结构,它以一种易于遍历的形式存储集合数据集的一小部分。索引存储一个或一组特定字段的值,按字段的值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB可以通过使用索引中的排序返回排序后的结果。

MongoDB使用的存储引擎是WiredTiger,其中索引构建使用的是 B+ tree 。

比如基于 B+ tree 的索引数据结构图示如下:

1.1、单键索引

如基于主键ID 进行的B+ tree 数据结构:
在这里插入图片描述

1.2、复合索引

复合索引只能支持前缀子查询

基于(name, age, position)建立的复合索引

以下排列会走索引:
name
name age
name age postion

以下排列不会走索引:
age :
position
age position
在这里插入图片描述

2、索引的特点

索引支持更快的查询

更快的排序

3、默认id索引

在创建集合期间,MongoDB 在_id字段上创建唯一索引。该索引可防止客户端插入两个具有相同值的文档。你不能将_id字段上的index删除。

二、实战

数据构造:

db.members.insertMany(
[
  {name:"zhangsan",age:19,tags:["00","It","SH"]},
  {name:"lisi",age:35,tags:["80","It","BJ"]},     
  {name:"wangwu",age:31,tags:["90","It","SZ"]}
]
);

1、索引查询

db.collection.getIndexes()

在这里插入图片描述
可以看到生成的集合默认是存在索引的。

  • v:索引版本
  • key:针对哪个字段生成的所有,这里可以看到是针对id生成了索引;这里的1表示索引是按照升序构建的
  • name:索引名称

2、索引创建

db.collection.createIndex(<keys>, <options>)
  • < keys> 指定了创建索引的字段
  • < options>可以指定成1或者-1表示升序或降序

2.1、创建一个单键索引

db.members.createIndex({name:1});

在这里插入图片描述
索引的默认名称是索引键和索引中每个键的方向(即1或-1)的连接,使用下划线作为分隔符, 也可以通过指定 name 来自定义索引名称;

db.members.createIndex({name:1}{ name: "whatever u like."});

对于单字段索引和排序操作,索引键的排序顺序(升序或降序)并不重要,因为MongoDB可以从任何方向遍历索引。

2.2、创建一个复合索引

MongoDB支持在多个字段上创建用户定义索引,即 复合索引。

复合索引中列出的字段的顺序具有重要意义。如果一个复合索引由 {name: 1, age: -1} 组成,索引首先按name 升序排序,然后在每个name值内按 age 降序 排序。

db.members.createIndex({ name:1,age:-1});

对于复合索引和排序操作,索引键的排序顺序(升序或降序)可以决定索引是否支持排序操作。

2.3、创建多键索引

MongoDB使用多键索引来索引存储在数组中的内容。如果索引包含数组值的字段,MongoDB为数组的每个元素创建单独的索引项。数组字段中的每一个元素,都会在多键索引中创建一个键。

db.members.createIndex( { tags:1})

2.4、删除索引

可以根据名称删除:

db.members.dropIndex("索引名称")

在这里插入图片描述
也可以根据定义删除:

db.members.dropIndex({name:1})

在这里插入图片描述

3、索引的效果解析

db.collection.explain().method(?)

可以使用 explain 进行分析的操作包含 aggregate, count, distinct, find ,group, remove, update.

这里我们需要重点关注winningPlan中stage 的值含义:

  • COLLSCAN: 整个集合扫描
  • IXScan: 索引扫描
  • FETCH: 根据索引指向的文档的地址进行查询(相当于mysql中的回表查询)
  • PROJECTION_COVERED:映射覆盖,不需要回表查询
  • SORT: 需要再内存中排序,效率不高

查询之前我们清空members集合中的所有索引!
在这里插入图片描述
现在没有添加任何索引,所以我们按照name过滤的时候就需要扫描整个集合。

我们现在来给name字段加一个索引,然后再来查看执行计划:

db.members.createIndex({name:1})
db.members.explain().find({name:"xxx"})
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "demo.members",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"name" : {
				"$eq" : "xxx"
			}
		},
		"queryHash" : "01AEE5EC",
		"planCacheKey" : "4C5AEA2C",
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"name" : 1
				},
				"indexName" : "name_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"name" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"name" : [
						"[\"xxx\", \"xxx\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "jihu1",
		"port" : 27017,
		"version" : "4.4.2",
		"gitVersion" : "15e73dc5738d2278b688f8929aee605fe4279b0e"
	},
	"ok" : 1
}

可以看到,现在的winningPlan.stage变成了FETCH,表示根据索引指向的文档的地址进行查询。

此时我们可以再关注一下winningPlan.inputStage中的值:

其中的stage是IXSCAN,表示索引扫描。

其实这里是可以进行优化的。我们可以使用覆盖查询来进行优化。

4、覆盖查询

当查询条件和查询的<投影>只包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。这些覆盖的查询可能非常高效。

db.members.explain().find({ name:"zhangsan"},{_id:0, name:1});

这时不需要 fetch, 可以直接从索引中获取数据。
在这里插入图片描述
可以看到此时winningPlan.stage是PROJECTION_COVERED,此时MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存。

我们在实际的使用中要尽量优化成 PROJECTION_COVERED。

db.members.explain().find().sort( {name:1 ,age:-1}) ;

使用已创建索引的字段进行排序,能利用索引的顺序,不需要重新排序,效率高。

 db.members.explain().find().sort( {name:1 ,age: 1}) ;

在这里插入图片描述
此时的winningPlan.stage是SORT.

使用未创建索引的字段进行排序, 因为和创建索引时的顺序不一致,所以需要重新排序,效率低。


如果需要更改某些字段上已经创建的索引,必须首先删除原有索引,再重新创建新索引,否则,新索引不会包含原有文档。

db.collection.dropIndex()

5、索引的唯一性

索引的unique属性使MongoDB拒绝索引字段的重复值。

除了唯一性约束,唯一索引和MongoDB其他索引功能上是一致的。

db.members.createIndex({age:1},{unique:true});

如果文档中的字段已经出现了重复值,则不可以创建该字段的唯一性索引。

如果新增的文档不具备加了唯一索引的字段,则只有第一个缺失该字段的文档可以被添加,索引中该键值被置为null。

复合键索引也可以具有唯一性,这种情况下,不同的文档之间,其所包含的复合键字段值的组合不可以重复。

6、索引的稀疏性

索引的稀疏属性可确保索引仅包含具有索引字段的文档的条目

索引会跳过没有索引字段的文档

可以将稀疏索引与唯一索引结合使用,以防止插入索引字段值重复的文档,并跳过索引缺少索引字段的文档。

准备数据:

db.sparsedemo.insertMany([{name:"xxx",age:19},{name:"zs",age:20}]);

创建 唯一键,稀疏索引:

db.sparsedemo.createIndex({name:1},{unique:true ,sparse:true});

如果同一个索引既具有唯一性,又具有稀疏性,就可以保存多篇缺失索引键值的文档了。

db.sparsedemo.insertOne({name:"zs2w",age:20});

db.sparsedemo.insertOne({name:"zs2w2",age:20});

说明:如果只单纯的 唯一键索引,则 缺失索引键的字段,只能有一个

复合键索引也可以具有稀疏性,在这种情况下,只有在缺失复合键所包含的所有字段的情况下,文档才不会被加入到索引中。

7、索引的生存时间

针对日期字段,或者包含了日期元素的数组字段,可以使用设定了生存时间的索引,来自动删除字段值超过生存时间的文档。

构造数据:

db.members.insertMany( [ 
    {
     name:"zhangsanss",
     age:19,tags:["00","It","SH"],
     create_time:new Date()}
    ] );
db.members.createIndex({ create_time: 1},{expireAfterSeconds:30 });

在create_time字段上面创建了一个生存时间是30s的索引。

复合键索引不具备生存时间的特性

当索引键是包含日期元素的数组字段时,数组中最小的日期将被用来计算文档是否已经过期。

数据库使用一个后台线程来监测和删除过期的文档,删除操作可能会有一定的延迟 。

https://docs.mongodb.com/manual/core/index-compound/#index-ascending-and-descending

https://docs.mongodb.com/manual/reference/operator/aggregation/addFields/#pipe._S_addFields

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值