MongoDB单值索引、复合索引、数组索引、地理空间索引、唯一索引、TTL索引、条件索引、稀疏索引、文本索引、模糊索引
索引主要表现为一种目录式的数据结构,用来实现快速数据查询。索引是对数据库表(集合)中的某些字段进行抽取、排列后,形成一种非常易于遍历读取的数据集合。使用WiredTiger存储引擎: B+ 结构注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex()
概述
索引主要表现为一种目录式的数据结构,用来实现快速数据查询。索引是对数据库表(集合)中的某些字段进行抽取、排列后,形成一种非常易于遍历读取的数据集合。
使用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: 需要再内存中排序,效率不高
- winningPlan:胜出的执行计划,数据库经过一系列优化后的计划。 stage 的值含义如下
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}})
更多推荐
所有评论(0)