当咱们查询数据库时,若是不创建索引。默认是经过咱们编写的规则去遍历数据库中全部的文档,最后找出符合条件的文档。这在数据量不是很大的时候问题不是很大,可是若是数据量很大,查询有可能花费数秒甚至数分钟的时间。mongodb
而索引会将数据按照必定的顺序进行排序,当咱们查询的时候经过这个顺序就会很快的查询出来(O(logN)的时间复杂度)数据库
内部原理 当咱们往数据库中存储数据时,经过底层的存储引擎持久化以后,会记录文档的位置信息,经过这个位置信息就能查找到对应的文档数组
例如:咱们在数据库中插入如下信息bash
> db.test.find()
{ "_id" : ObjectId("5d47f95c4903b485d29ba952"), "name" : "fqr0", "age" : 0 }
{ "_id" : ObjectId("5d47f95c4903b485d29ba953"), "name" : "fqr1", "age" : 1 }
{ "_id" : ObjectId("5d47f95c4903b485d29ba954"), "name" : "fqr2", "age" : 2 }
复制代码
数据库会记录文档的位置信息app
位置信息 | 文档 |
---|---|
位置1 | {"name":"far1", "age":1} |
位置2 | {"name":"far0", "age":0} |
位置3 | {"name":"far2", "age":2} |
这时咱们要查询find({"age":2})
时,会遍历全部的三个文档,当数据量很大时查询会很慢,若是咱们想加快查询速度,就能够对age
字段加索引dom
db.test.createIndex({"age":1}) // 按照age字段建立升序序列
复制代码
创建索引后性能
age | 位置信息 |
---|---|
0 | 位置2 |
1 | 位置1 |
2 | 位置3 |
这样就不用遍历全部的文档去查找符合条件{"age":2}
的数据了测试
其实在MongoDB文档中都有一个_id
字段,它就是一个索引,用来经过_id
快速的查询文档优化
索引的好处ui
age
字段排序,就不须要再去遍历全部文档了单字段索引 就是只对单个字段进行索引
db.test.createIndex({"age":1})
复制代码
1 表示升序,-1表示降序
单字段索引是最经常使用的索引方式,MongoDB默认建立的_id
索引就是这种方式
复合段索引 对多个字段进行索引
db.test.createIndex({"age":1, "name":1})
复制代码
多字段索引的方式是若是文档的age
相同,就经过name
字段排序
例如:
age,name | 位置信息 |
---|---|
0,fqr0 | 位置2 |
0,far1 | 位置1 |
2,fqr1 | 位置3 |
注意:采用这种索引创建方式,不只能知足多个字段的查询find({"age":0,"name":"fqr1"})
,也能够知足单个字段的查询find({"age":0})
。可是,find({"name":"fqr0"})
是利用不了索引的
采用这种方式时,一般选择不会容易重复的字段做为第一个条件,这样性能会更好
多Key索引
若是一个字段为数组时,对这个字段创建索引就是多key索引,数据库会为其中的每一个元素创建索引
db.test.createIndex({"field": 1})
复制代码
不经常使用的索引
MongoDB支持对数据库的操做进行分析,记录操做比较慢的动做。一共有三个level
设置level
> db.setProfilingLevel(1)
{ "was" : 1, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }
复制代码
查看level
> db.getProfilingLevel()
1
复制代码
咱们下面测试一下profile
首先在数据库中写入大量的数据
for(let i=0;i<1000000;i++){db.test.insertOne({"name":"fqr"+i,"age":parseInt(i * Math.random())}) }
复制代码
没有创建索引前查询都是全表扫描
> db.test.find({"name":"fqr1234"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.test",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "fqr1234"
}
},
"winningPlan" : {
"stage" : "COLLSCAN", // 全表扫描
"filter" : {
"name" : {
"$eq" : "fqr1234"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"ok" : 1
}
复制代码
咱们查看system.profile
,发现已经记录了这条数据,由于查询时间是1202毫秒,超过了默认值100毫秒
> db.system.profile.find().sort({$natural:-1}).limit(1).pretty()
{
"op" : "command",
"ns" : "test.test",
"command" : {
"explain" : {
"find" : "test",
"filter" : {
"name" : "fqr1234"
}
},
"verbosity" : "allPlansExecution",
"$db" : "test"
},
"numYield" : 7834,
"locks" : {
"Global" : {
"acquireCount" : {
"r" : NumberLong(7835)
}
},
"Database" : {
"acquireCount" : {
"r" : NumberLong(7835)
}
},
"Collection" : {
"acquireCount" : {
"r" : NumberLong(7835)
}
}
},
"responseLength" : 862,
"protocol" : "op_msg",
"millis" : 1202,
"ts" : ISODate("2019-08-06T02:14:59.455Z"),
"client" : "127.0.0.1",
"appName" : "MongoDB Shell",
"allUsers" : [ ],
"user" : ""
}
复制代码
咱们优化查询速度时,能够根据system.profile
中的记录来创建相关字段的索引,提高查询速度
下面咱们创建一个索引
> db.test.createIndex({"name":1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
复制代码
再进行查询
> db.test.find({"name":"fqr12345"}).explain("allPlansExecution")
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.test",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "fqr12345"
}
},
"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" : [
"[\"fqr12345\", \"fqr12345\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 4,
"totalKeysExamined" : 1,
"totalDocsExamined" : 1,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"fqr12345\", \"fqr12345\"]"
]
},
"keysExamined" : 1,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
},
"allPlansExecution" : [ ]
},
"ok" : 1
}
复制代码
会发现查询已经再也不是全表扫描了,而是根据索引查询,而且查询速度由原来的1202ms提高到了如今的4ms
查看索引
> db.test.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.test"
},
{
"v" : 2,
"key" : {
"name" : 1
},
"name" : "name_1",
"ns" : "test.test"
}
]
复制代码
指定须要使用的索引
> db.test.find({"name":"fqr12345"}).hint({"name":1}).pretty()
{
"_id" : ObjectId("5d47f9604903b485d29bd98b"),
"name" : "fqr12345",
"age" : 5526
}
复制代码
删除索引
> db.test.dropIndex("name_1")
# 删除全部索引
> db.test.dropIndex("*")
复制代码