共计 5871 个字符,预计需要花费 15 分钟才能阅读完成。
MongoDB
的索引和 MySql
的索引的作用和优化要遵循的准则根本类似,MySql
索引类型根本能够辨别为:
- 单键索引 – 联结索引
- 主键索引(聚簇索引) – 非主键索引(非聚簇索引)
在 MongoDB
中除了这些根底的分类之外, 还有一些非凡的索引类型, 如: 数组索引 | 稠密索引 | 天文空间索引 | TTL 索引等.
为了上面不便测试咱们应用脚本插入以下数据
for(var i = 0;i < 100000;i++){
db.users.insertOne({
username: "user"+i,
age: Math.random() * 100,
sex: i % 2,
phone: 18468150001+i
});
}
单键索引
单键索引即索引的字段只有一个, 是最根底的索引形式.
在汇合中应用 username
字段, 创立一个单键索引,MongoDB
会主动将这个索引命名为username_1
db.users.createIndex({username:1})
'username_1'
在创立索引后查看一下应用 username
字段的查问打算,stage
为 IXSCAN
代表应用应用了索引扫描
db.users.find({username:"user40001"}).explain()
{
queryPlanner:
{
winningPlan:
{
......
stage: 'FETCH',
inputStage:
{
stage: 'IXSCAN',
keyPattern: {username: 1},
indexName: 'username_1',
......
}
}
rejectedPlans: [] ,},
......
ok: 1
}
在索引优化的准则当中, 有很重要的准则就是索引要建设在基数高的的字段上, 所谓基数就是一个字段上不反复数值的个数, 即咱们在创立 users
汇合时年龄呈现的数值是 0-99
那么 age
这个字段将会有 100 个不反复的数值, 即 age
字段的基数为 100, 而 sex
这个字段只会呈现 0 | 1
这个两个值, 即 sex
字段的根底是 2, 这是一个相当低的基数, 在这种状况下, 索引的效率并不高并且会导致索引生效.
上面就船舰一个 sex
字段索引, 来查问执行打算会发现, 查问时是走的全表扫描, 而没有走相干索引.
db.users.createIndex({sex:1})
'sex_1'
db.users.find({sex:1}).explain()
{
queryPlanner:
{
......
winningPlan:
{
stage: 'COLLSCAN',
filter: {sex: { '$eq': 1} },
direction: 'forward'
},
rejectedPlans: []},
......
ok: 1
}
联结索引
联结索引即索引上会有多个字段, 上面应用 age
和sex
两个字段创立一个索引
db.users.createIndex({age:1,sex:1})
'age_1_sex_1'
而后咱们应用这两个字段进行一次查问, 查看执行打算, 顺利地走了这条索引
db.users.find({age:23,sex:1}).explain()
{
queryPlanner:
{
......
winningPlan:
{
stage: 'FETCH',
inputStage:
{
stage: 'IXSCAN',
keyPattern: {age: 1, sex: 1},
indexName: 'age_1_sex_1',
.......
indexBounds: {age: [ '[23, 23]' ], sex: ['[1, 1]' ] }
}
},
rejectedPlans: [],},
......
ok: 1
}
数组索引
数组索引就是对数组字段创立索引, 也叫做多值索引, 上面为了测试将 users
汇合中的数据减少一部分数组字段.
db.users.updateOne({username:"user1"},{$set:{hobby:["唱歌","篮球","rap"]}})
......
创立数组索引并进行查看其执行打算, 留神 isMultiKey: true
示意应用的索引是多值索引.
db.users.createIndex({hobby:1})
'hobby_1'
db.users.find({hobby:{$elemMatch:{$eq:"钓鱼"}}}).explain()
{
queryPlanner:
{
......
winningPlan:
{
stage: 'FETCH',
filter: {hobby: { '$elemMatch': { '$eq': '钓鱼'} } },
inputStage:
{
stage: 'IXSCAN',
keyPattern: {hobby: 1},
indexName: 'hobby_1',
isMultiKey: true,
multiKeyPaths: {hobby: [ 'hobby'] },
......
indexBounds: {hobby: [ '[" 钓鱼 "," 钓鱼 "]' ] } }
},
rejectedPlans: []},
......
ok: 1
}
数组索引相比于其它索引来说索引条目和体积必然呈倍数减少, 例如均匀每个文档的 hobby
数组的 size
为 10, 那么这个汇合的 hobby
数组索引的条目数量将是一般索引的 10 倍.
联结数组索引
联结数组索引就是含有数组字段的联结索引, 这种索引不反对一个索引中含有多个数组字段, 即一个索引中最多能有一个数组字段, 这是为了防止索引条目爆炸式增长, 假如一个索引中有两个数组字段, 那么这个索引条目标数量将是一般索引的 n * m 倍
天文空间索引
在原先的 users
汇合上, 减少一些地理信息
for(var i = 0;i < 100000;i++){
db.users.updateOne({username:"user"+i},
{
$set:{
location:{
type: "Point",
coordinates: [100+Math.random() * 4,40+Math.random() * 3]
}
}
});
}
创立一个二维空间索引
db.users.createIndex({location:"2dsphere"})
'location_2dsphere'
// 查问 500 米内的人
db.users.find({
location:{
$near:{$geometry:{type:"Point",coordinates:[102,41.5]},
$maxDistance:500
}
}
})
天文空间索引的 type
有很多蕴含Ponit(点)
| LineString(线)
| Polygon(多边形)
等
TTL 索引
TTL 的全拼是 time to live
, 次要是用于过期数据主动删除, 应用这种索引须要在文档中申明一个工夫类型的字段, 而后为这个字段创立 TTL 索引的时候还须要设置一个expireAfterSeconds
过期工夫单位为秒, 创立实现后 MongoDB
会定期对汇合中的数据进行查看, 当呈现:
$$
以后工夫 – TTL 索引字段时间 > expireAfterSrconds
$$
MongoDB
将会主动将这些文档删除, 这种索引还有以下这些要求:
- TTL 索引只能有一个字段, 没有联结 TTL 索引
- TTL 不能用于固定汇合
- TTL 索引是一一遍历后, 发现满足删除条件会应用
delete
函数删除, 效率并不高
首先在咱们文档上增减一个工夫字段
for(var i = 90000;i < 100000;i++){
db.users.updateOne({username:"user"+i},
{
$set:{createdDate:new Date()
}
});
}
创立一个 TTL 索引并且设定过期工夫为 60s, 待过 60s 后查问, 会发现这些数据曾经不存在
db.users.createIndex({createdDate:1},{expireAfterSeconds:60})
'createdDate_1'
另外还能够用 CollMod
命令更改 TTL 索引的过期工夫
db.runCommand({
collMod:"users",
index:{keyPattern:{createdDate:1},
expireAfterSeconds:120
}
})
{expireAfterSeconds_old: 60, expireAfterSeconds_new: 120, ok: 1}
条件索引
条件索引也叫局部索引(partial), 只对满足条件的数据进行建设索引.
只对 50 岁以上的 user
进行建设 username_1
索引, 查看执行打算会发现 isPartial
这个字段会变成true
db.users.createIndex({username:1},{partialFilterExpression:{age:{$gt:50}
}})
'username_1'
db.users.find({$and:[{username:"user4"},{age:60}]}).explain()
{
queryPlanner:
{
......
winningPlan:
{
stage: 'FETCH',
filter: {age: { '$eq': 60} },
inputStage:
{
stage: 'IXSCAN',
keyPattern: {username: 1},
indexName: 'username_1',
......
isPartial: true,
......
}
},
rejectedPlans: []},
......
ok: 1
}
稠密索引
个别的索引会依据某个字段为整个汇合创立一个索引, 即便某个文档不存这个字段, 那么这个索引会把这个文档的这个字段当作 null
建设在索引当中.
稠密索引不会对文档中不存在的字段建设索引, 如果这个字段存在然而为 null
时, 则会创立索引.
上面给 users
汇合中的局部数据创立稠密索引
for(var i = 5000;i < 10000;i++){if(i < 9000){
db.users.updateOne({username:"user"+i},
{$set:{email:(120000000+i)+"@qq.email"}}
)
}else{
db.users.updateOne({username:"user"+i},
{$set:{email:null}}
)
}
}
当不建设索引应用 {email:null}
条件进行查问时, 咱们会发现查出来的文档蕴含没有 email
字段的文档
db.users.find({email:null})
{_id: ObjectId("61bdc01ba59136670f6536fd"),
username: 'user0',
age: 64.41483801726282,
sex: 0,
phone: 18468150001,
location:
{
type: 'Point',
coordinates: [101.42490900320335, 42.2576650823515]
}
}
......
而后对 email
这个字段创立一个稠密索引应用 {email:null}
条件进行查问, 则发现查问来的文档全副是 email
字段存在且为 null
的文档.
db.users.createIndex({email:1},{sparse:true});
'email_1'
db.users.find({email:null}).hint({email:1})
{_id: ObjectId("61bdc12ca59136670f655a25"),
username: 'user9000',
age: 94.18397576757012,
sex: 0,
phone: 18468159001,
hobby: ['钓鱼', '乒乓球'],
location:
{
type: 'Point',
coordinates: [101.25903151863596, 41.38450145025062]
},
email: null
}
......
文本索引
文本索引将建设索引的文档字段先进行分词再进行检索, 然而目前还不反对中文分词.
上面减少两个文本字段, 创立一个联结文本索引
db.blog.insertMany([{title:"hello world",content:"mongodb is the best database"},
{title:"index",content:"efficient data structure"}
])
// 创立索引
db.blog.createIndex({title:"text",content:"text"})
'title_text_content_text'
// 应用文本索引查问
db.blog.find({$text:{$search:"hello data"}})
{_id: ObjectId("61c092268c4037d17827d977"),
title: 'index',
content: 'efficient data structure'
},
{_id: ObjectId("61c092268c4037d17827d976"),
title: 'hello world',
content: 'mongodb is the best database'
}
惟一索引
惟一索引就是在建设索引地字段上不能呈现反复元素, 除了单字段惟一索引还有联结惟一索引以及数组惟一索引 (即 数组之间不能有元素交加 )
// 对 title 字段创立惟一索引
db.blog.createIndex({title:1},{unique:true})
'title_1'
// 插入一个曾经存在的 title 值
db.blog.insertOne({title:"hello world",content:"mongodb is the best database"})
MongoServerError: E11000 duplicate key error collection: mock.blog index: title_1 dup key: {: "hello world"}
// 查看一下执行打算,isUnique 为 true
db.blog.find({"title":"index"}).explain()
{
queryPlanner:
{
......
winningPlan:
{
stage: 'FETCH',
inputStage:
{
stage: 'IXSCAN',
keyPattern: {title: 1},
indexName: 'title_1',
isMultiKey: false,
multiKeyPaths: {title: [] },
isUnique: true,
......
}
},
rejectedPlans: []},
.......
ok: 1
}