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为truedb.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 }