概述

索引主要表现为一种目录式的数据结构,用来实现快速数据查询。索引是对数据库表(集合)中的某些字段进行抽取、排列后,形成一种非常易于遍历读取的数据集合。

使用WiredTiger存储引擎: B+ 结构

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

  • 创建5w数据测试索引,如果shell操作不方便可以直接在Navicat上执行。

    for (var i = 0; i < 50000; i++) {
    var identificationTime = "2021"+"-0"+Math.ceil(Math.random()*9)+"-"+(Math.ceil(Math.random()*19)+9)+" "+(Math.ceil(Math.random()*14)+9)+":"+(Math.ceil(Math.random()*50)+9)+":"+(Math.ceil(Math.random()*50)+9);
    var createDate = identificationTime.split(" ")[0];
    db.machine.insert( {
        deviceSn: String(456),
        deviceName: i+"清洗设备"+i,
        uuid: NumberLong("1468775476498")+i,
        responsiblPerson: i+"负责人"+i,
        responsiblPhone: i+"0"+i,
        faceUrl: "/202112-6ca108039006464c8a7af138c0301b07.jpg",
        areaCheckCode: "1",
        regionId: "784",
        similarity: 85,
        customerId: "9516303150"+i,
        temperature: Math.ceil(Math.random()*2)+34,
        identificationTime: identificationTime,
        inoutType: NumberInt("0"),
        verifyType: "0",
        status: (Math.ceil(Math.random()*3)-1),
        createDate: createDate,
    	tags:["t"+Math.ceil(Math.random()*3),"a"+Math.ceil(Math.random()*3),"b"+Math.ceil(Math.random()*3)],
    	location: {
    			type:"Point",
    			coordinates:[37.2222+Math.ceil(Math.random()*3),122.3333+Math.ceil(Math.random()*3)]
    		}
    		
    	} );
    }
    
  • 查询集合中索引

    db.machine.getIndexes();
    

    在这里插入图片描述

  • 删除索引,根据名字删除索引

    db.machine.dropIndex('createDateIndex');
    

    在这里插入图片描述

  • explain分析执行性能

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

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

    • winningPlan:胜出的执行计划,数据库经过一系列优化后的计划。 stage 的值含义如下
      • COLLSCAN: 整个集合扫描
      • IXScan: 索引扫描
      • FETCH: 根据索引指向的文档的地址进行查询;相当于mysql中的回表查询,没有索引覆盖
      • SORT: 需要再内存中排序,效率不高

    在这里插入图片描述

1、单值索引和复合索引

复合索引是对多个字段组合而成的索引,符合索引中字段的顺序,字段的升降序对查询性能有直接的影响。

索引的默认名称是索引键和索引中每个键的方向(即1升序或-1降序)的连接,使用下划线作为分隔符, 也可以通过指定 name 来自定义索引名称;

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

db.machine.createIndex({createDate:1,status:1})

创建单值索引,并指定索引名字

db.machine.createIndex({createDate:1},{name:'createDateIndex'})

1:代表升序,单值索引升降序意义不大
createDate、status:代表字段(内嵌文档字段创建索引同理parent.x)
默认索引名字:createDate_1_status_1

复合索引只能支持前缀子查询:例如基于name, age, position 建立的复合索引
支持:

  • name

  • name age

  • name age postion

  • age

  • position

  • age position

不同于mysql 必须满足最左原则,但是有点类似

1.1、explaiin执行计划查看

在这里插入图片描述

1.2、优化器

查询优化器优化后的结构依然是走索引的。如下,查询和创建的顺序是不一样的
在这里插入图片描述

1.3、索引全覆盖

索引全覆盖不需要回表查询
在这里插入图片描述

1.4、索引排序打乱索引结构

排序方式与索引结构顺序不一致,导致查询时内存重排序,影响性能。甚至文件大的时候导致持久化磁盘临时文件,重排序后返回。

db.machine.explain().find({createDate:'2021-05-25',status:1},{_id:0,createDate:1,status:1}).sort({createDate:1,status:-1})

在这里插入图片描述

2、数组索引

数组索引又称多值索引(multikey index),当对数组类型字段创建索引时,这个索引就是多值的。多值索引在使用上与普通索引并没有什么不同,只是在索引键上会产生多个值,多值索引仅仅是一个字段上出现了多值,比如复合索引中出现了多个数组字段是不允许的

db.machine.createIndex({createDate:1,tags:1}, {name:'multiKeyIndex'})

例如数据:{_id:1,tags:[1,2,3]}
多值索引:1->1,1->2,1->3,会产生多条索引数据

在这里插入图片描述

3、地理空间索引

MongoDB是支持地理空间搜索的。地理空间索引(2dsphere index)就是专门用于实现位置检索的一种特殊索引

例如:外卖平台中显示商家距离位置,或者查询附近商家都是可以用MongoDB来实现查询

db.city.insertMany(
	[{
		name:"北京",
		location: {
			"type": "Point",
			coordinates:[116.397448,39.909208]
		}
	},{
		name:"廊坊",
		location: {
			"type": "Point",
			coordinates:[116.673136,39.569677]
		}
	},{
		name:"唐山",
		location: {
			"type": "Point",
			coordinates:[118.189249,39.637394]
		}
	},{
		name:"石家庄",
		location: {
			"type": "Point",
			coordinates:[114.497843,38.05455]
		}
	},{
		name:"大兴区",
		location: {
			"type": "Point",
			coordinates:[116.371012,39.705045]
		}
	}
]);
# 创建索引
db.city.createIndex({location:"2dsphere"}, {name:"2dsphereIndex"})

注意:如果随便造数据也是要复合坐标,否则创建索引时会报错“Can’t extract geo keys:”

如上例子中:location字段是一个内嵌型文档,用于表明商家的地理位置,type:'Point’表示这是地图上的一个点,coordinates则是经纬度

在MongoDB中,地理数据相关的索引有两种2dsphere 和 2d

  • 地球状球体计算几何的查询应使用 2dsphere 索引。
  • 2d 索引在二维平面上使用存储为点的数据的索引。该 2d 索引适用于MongoDB 2.2及更早版本中使用的传统坐标对。

3.1、$near按距离搜索

在这里插入图片描述

  • $near:按照距离检索,并对返回结果按距离排序
  • $geometry:用于制定一个GeoJSON格式的地理空间对象
  • type:“Point”:标识地理坐标
  • coordinates:查询的经纬度
  • $maxDistance:限定了最大距离,单位米
db.city.find({
	location:{
		$near:{
			$geometry:{
				type:"Point",coordinates:[116.397448,39.909208]
			},
			$maxDistance:250000
		}
	}
})

在这里插入图片描述
官方文档地址

4、唯一索引

在创建索引时,通过指定unique=true选项可以将其声明唯一索引。

db.city.createIndex({name:1}, {name:"nameUniqueIndex",unique:true})

创建重复数据报错“E11000 duplicate key error collection”
在这里插入图片描述
唯一索引约束严格按照写入顺序进行比较的,所以在复合型、嵌套型、数组型唯一索引都需要注意写入顺序,例如:type、name唯一约束,写入type:1,name:2后在写入name:2,type:1是可以成功的。数组唯一索引是无法保证数组内重复元素的

注意要点:

  • 唯一索引对于文档中缺失的字段,会使用null代替,因此不允许存在多个文档缺失索引字段的情况
    在这里插入图片描述
  • 对于分片集合,唯一索引必须匹配分片规则,为了保证全局唯一性,分片键必须作为唯一索引的前缀字段

5、TTL索引

在一般的应用系统中,并非所有的数据都需要永久存储。例如一些系统事件、用户消息等,这些数据随着事件的推移,其重要程度逐渐降低。更重要的是存储这些历史数据需要花费较高的成本,因此项目中通常会对过期且不再使用的数据进行老化处理

  • 方案一:为每个数据记录一个时间戳,应用侧开启一个定时器,按照时间戳定期删除过期数据
  • 方案二:数据按照日期进行分表,同一维度的数据归到一张表中,同样使用定时器删除过期的表

MongoDB中对于数据老化处理,提供了一种更加便捷的做法:TTL(Time To Live)TTL需要声明在一个日期类型的字段中,集合创建TTL索引后,MongoDB会在周期性运行的后台线程中对该集合进行检查及数据清理工作,除了老化数据功能外,TTL索引具有普通索引一样的功能,可以用于数据加速查询

MongoDB后台线程的检测间隔是60秒;

例如,如下数据

db.systemlog.insertOne({
	createDate:new Date(),
	type:'error',
	message:'E11000 duplicate key error collection'
});

5.1、固定多少秒后过期

# 创建TTL索引
db.systemlog.createIndex({createDate:1}, {name:"expireIndex",expireAfterSeconds:10})

expireAfterSeconds:10表示数据将在createDate之后10秒过期

5.2、指定过期时间点

有些时候我们不希望在记录创建时刻之后的多少秒再删除(因为这样相当于固定死了失效间隔),而是希望在程序运行时指定某个时刻 (例如避开流量的高峰期) 进行失效;MongoDB中需要expireAfterSeconds设置为0,同时需要指定过期的字段(必须是时间类型)

expireAfterSeconds:0 表示MongoDB将用索引字段,也就是expiredDate的时间值加0秒后的时间(即expiredDate的值本身)作为判断数据失效的依据;

db.consume_message.createIndex({ "expiredDate": 1 }, {expireAfterSeconds: 0})

5.3、修改索引过期时间

index.name:匹配索引名称
index.keyPattern:匹配索引创建时的对象例如创建索引时:{createDate:1}

db.runCommand({
	collMod:'systemlog',
	index:{
		name:'expireIndex',
		expireAfterSeconds:20
	}
})

5.4、注意项

TTL索引确实可以减少开发的工作量,而且通过数据库自动清理的方式会更高效、可靠,但是使用TTL索引需要注意以下条件:

  • TTL索引只能支持单个字段,必须是非_id字段

  • TTL索引不能用于固定集合(创建集合时设置该集合中文档的数目,固定集合)

  • TTL索引无法保证及时的数据老化,MongoDB会通过后台的TTL Monitor定时器来清理老化数据,默认1分钟。当数据库负载过高的情况下,TTL的行为则会推迟

  • TTL索引对于数据清理仅仅使用了remove命令,这种方式不是很高效,因此TTL Monitor在运行期间对系统CPU、磁盘都会造成一定的压力。相比之下,日期分表的方式操作会更高效

6、条件索引

条件(partial)索引允许只对部分分档建立索引

例如,仅对业务上最常用于查询的数据集创立索引,可以节省一些空间

对于菜品中评分大于5的数据创建cuisine和name索引

db.restaurants.createIndex({cuisine:1,name:1}, {name:"partialIndex",partialFilterExpression:{rating:{$gt:5}}})

7、稀疏索引(sparse=true)

由于MongoDB非结构化数据,一个集合中允许结构完全不同的两个文档共存。对于索引来说,意味着可能某些文档中并不存在该字段,但MongoDB会将不存在字段的情况等同于null处理。稀疏索引则具备,只对存在字段的文档进行索引(包括字段值为null的文档)的特性
例如

db.test.insertOne({x:1})

db.test.insertOne({x:1,y:null})

在这里插入图片描述

查询y=null时2个文档同时被返回了,但是其中一个文档中并没有y字段。

创建稀疏索引

db.test.createIndex({y:1},{name:'yIndex',sparse:true})

使用hint继续查询,hint操作强制查询优化器选择某个特殊的索引,这样扫描的内容都是索引内容

db.test.find({y:null}).hint({y:1})

在这里插入图片描述
查看执行计划

db.test.find({y:null}).hint({y:1}).explain()

在这里插入图片描述

8、文本索引

MongoDB支持全文检索功能(企业版支持中文分词,当前我还是想用es)

后续补充

9、模糊索引

MongoDB的文档模式是动态的结构可变,而模糊索引可建立在一些不预知的字段上,达到查询加速的目的,4.2以上版本支持

例如,一个商品有多个属性,不同的商品,属性又可能不一样。

db.goods.insertMany([
	{name:'手表',attributes:{height:120,price:1500}},
	{name:'电话',attributes:{color:'red',price:1500}},
	{name:'电脑',attributes:{graphoscope:'2k',price:7500}}
]);

# 创建模糊索引
db.goods.createIndex({'attributes.$**':1},{name:'attributesVagueIndex'})

在这里插入图片描述

attributes.$**所有attributes字段作为开始路径的任何一个字段,都将采用索引

例如

db.goods.explain().find({'attributes.height':20});

db.goods.explain().find({'attributes.price':{$gt:1500}})

在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐