MongoDB高阶特性:索引
一、为什么使用B-Tree
一)B树和B+树
- B树的树内存储数据,因此查询单条数据的时候,B树的查询效率不固定,最好的情况是O(1)。我们可以认为在做单一数据查询的时候,使用B树平均性能更好。但是,由于B树中各节点之间没有指针相邻,因此B树不适合做一些数据遍历操作。
- B+树的数据只出现在叶子节点上,因此在查询单条数据的时候,查询速度非常稳定。因此,在做单一数据的查询上,其平均性能并不如B树。但是,B+树的叶子节点上有指针进行相连,因此在做数据遍历的时候,只需要对叶子节点进行遍历即可,这个特性使得B+树非常适合做范围查询。
因此,我们可以做一个推论:
没准是Mysql中数据遍历操作比较多,所以用B+树作为索引结构。而Mongodb是做单一查询比较多,数据遍历操作比较少,所以用B树作为索引结构。
那么为什么Mysql做数据遍历操作多?而Mongodb做数据遍历操作少呢? 因为Mysql是关系型数据库,而Mongodb是非关系型数据。
那为什么关系型数据库,做数据遍历操作多?而非关系型数据库,做数据遍历操作少呢?
二)关系型VS非关系型
假设,我们此时有两个逻辑实体:学生(Student)和班级(Class),这两个逻辑实体之间是一对多的关系。毕竟一个班级有多个学生,一个学生只能属于一个班级。
1、关系型数据库
我们在关系型数据库中,考虑的是用几张表来表示这二者之间的实体关系。常见的无外乎是,一对一关系,用一张表就行。一对多关系,用两张表。多对多关系,用三张表。那我们,此时要查 cname 为 1班 的班级,有多少学生怎么办?
假设 cname 这列,我们建了索引!执行SQL,如下所示!
SELECT * FROM t_student t1, (SELECT cid FROM t_class WHERE cname = '1班') t2 WHERE t1.cid = t2.cid
而这,就涉及到了数据遍历操作!
因为但凡做这种关联查询,你躲不开join操作的!既然涉及到了join操作,无外乎从一个表中取一个数据,去另一个表中逐行匹配,如果索引结构是B+树,叶子节点上是有指针的,能够极大的提高这种一行一行的匹配速度!
有的人或许会抬杠说,如果我先执行:
SELECT cid FROM t_class WHERE cname = '1班'
获得cid后,再去循环执行:
SELECT * FROM t_student WHERE cid = ...
就可以避开join操作呀?
对此,我想说。你确实避开了join操作,但是你数据遍历操作还是没避开。你还是需要在student的这张表的叶子节点上,一遍又一遍的遍历!
那在非关系型数据库中,我们如何查询 cname 为 1班 的班级,有多少学生?
2、非关系型数据库
执行两次查询去获得结果!一次去class集合查,获得id后再去student集合查。
确实,这么设计是可以的,我没说不行。只是不符合非关系型数据库的设计初衷。在MongoDB中,根本不推荐这么设计。虽然,Mongodb中有一个$lookup操作,可以做join查询。但是理想情况下,这个$lookup操作应该不会经常使用,如果你需要经常使用它,那么你就使用了错误的数据存储了(数据库):如果你有相关联的数据,应该使用关系型数据库(SQL)。
假设 name 这列,我们建了索引!
我只需执行一次语句:
db.class.find({name:'1班'})
这样就能查询出自己想要的结果。
而这,就是一种单一数据查询!毕竟你不需要去逐行匹配,不涉及遍历操作,幸运的情况下,有可能一次IO就能够得到你想要的结果。
因此,由于关系型数据库和非关系型数据的设计方式上的不同。导致在关系型数据中,遍历操作比较常见,因此采用B+树作为索引,比较合适。而在非关系型数据库中,单一查询比较常见,因此采用B树作为索引,比较合适。
二、MongoDB索引类型
众所周知,MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),_id是文档唯一的标识,为了保证能根据文档id快递查询文档,MongoDB默认会为集合创建_id字段的索引(默认索引)。
db.person.getIndexes() // 查询集合的索引信息
[
{
"ns" : "test.person", // 集合名
"v" : 1, // 索引版本
"key" : { // 索引的字段及排序方向
"_id" : 1 // 根据_id字段升序索引
},
"name" : "_id_" // 索引的名称
}
]
除此之外,MongoDB还支持多种类型的索引,包括单值索引、复合索引、多key(多值)索引、文本索引、哈希索引、地理位置索引等,每种类型的索引有不同的使用场合。
1、单值索引
db.person.createIndex({age: 1})
上述语句针对age创建了单字段索引,其能加速对age字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。{age: 1} 代表升序索引,也可以通过{age: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。
2、复合索引
复合索引是Single Field Index的升级版本,它针对多个字段联合创建索引,先按第一个字段排序,第一个字段相同的文档按第二个字段排序,依次类推,如下针对age, name这2个字段创建一个复合索引。
db.person.createIndex({age: 1, name: 1})
上述索引对应的数据组织类似下表,与{age: 1}索引不同的是,当age字段相同时,再根据name字段进行排序,所以pos5对应的文档排在pos3之前。
AGE,NAME 位置信息
18,adam pos5
18,jack pos3
19,jack pos1
20,rose pos2
21,tony pos4
复合索引能满足的查询场景比单字段索引更丰富,不光能满足多个字段组合起来的查询,比如db.person.find({age: 18, name: "jack"}),也能满足所有能匹配符合索引前缀的查询,这里{age: 1}即为{age: 1, name: 1}的前缀,所以类似db.person.find( {age: 18} )的查询也能通过该索引来加速;但db.person.find({name: "jack"})则无法使用该复合索引。如果经常需要根据『name字段』以及『name和age字段组合』来查询,则应该创建如下的复合索引
db.person.createIndex({name: 1, age: 1})
这里有个需要注意的地方:
对应上面的复合索引db.person.createIndex({name: 1, age: 1}) ,查询语句的索引使用顺序db.person.find({name: "adam ",age: 18})与db.person.find({age: 18,name: "adam"})相同,都会使用到复合索引db.person.createIndex( {name: 1, age: 1} ),因为MongoDB底层会对查询语句进行优化重新排列顺序(mysql也是同样的原理)。
除了查询的需求能够影响索引的顺序,字段的值分布也是一个重要的考量因素,即使person集合所有的查询都是『name和age字段组合』(指定特定的name和age),字段的顺序也是有影响的。
age字段的取值很有限,即拥有相同age字段的文档会有很多;而name字段的取值则丰富很多,拥有相同name字段的文档很少;显然先按name字段查找,再在相同name的文档里查找age字段更为高效。
3、多key(多值)索引
当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引,比如person表加入一个habbit字段(数组)用于描述兴趣爱好,需要查询有相同兴趣爱好的人就可以利用habbit字段的多key索引。
{"name": "jack", "age": 19, habbit: ["football", "runnning"]}
db.person.createIndex({habbit: 1}) // 自动创建多key索引
db.person.find({habbit: "football"})
4、文本索引
能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引。text索引可以包括其值为字符串或字符串元素数组的任何字段。
文本索引,顾名思义就是用于搜索文本的,可以用于搜索所有的value,也可以搜索指定的field对应的value。只要field对应value是string,或者对应的value是array且array中的元素是string,那么文本索引都可以索引该field
注意:一个集合最多只能有一个文本索引。
要创建text索引,请使用该 db.collection.createIndex()方法。要索引包含字符串或字符串元素数组的字段,请包含该字段并"text"在索引文档中指定字符串文字。
如以下示例所示:
db.collection.createIndex({keys:"text"})
也可以创建多个字段text,例如
db.collection.createIndex({subject:"text",comments:"text"})
举例,有两条记录
{_id:5908df789dfd1fd5884fd84f7df4,statement:MongoDB is the worst}
{_id:5908dfgfh587hgf15f4hf54hf418,statement:MongoDB is the best}
给statement字段创建文本索引:
db.collection.createIndex({statement:"text"})
查询
db.collection.find({$text:{$search:"MongoDB best"}})
查询出来的结果集是:
{_id:5908df789dfd1fd5884fd84f7df4,statement:MongoDB is the worst}
{_id:5908dfgfh587hgf15f4hf54hf418,statement:MongoDB is the best}
这是因为文本索引在查询时,每个单词之间的分隔是"或者",所以上面的查询语句的意思是:查询包含MongoDB或者best的记录数。
在多个字段上创建文本索引时,还可以使用通配符说明符($**)。使用通配符文本索引,MongoDB会为包含集合中每个文档的字符串数据的每个字段编制索引。
例如:
db.collection.createIndex({"$**","text"})
此索引允许在具有字符串内容的所有字段上进行文本搜索。如果不清楚要包含在文本索引中的哪些字段或用于临时查询
通配符文本索引可以是复合索引的一部分。例如,以下内容在字段nane和通配符说明符上创建复合索引,例如:
db.collection.createIndex({name:1,"$**","text"})
排序操作无法从text索引获取排序顺序,即使是复合文本索引也是如此; 即排序操作不能使用文本索引中的排序。
5、哈希索引
是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
6、地理位置索引
能很好的解决O2O的应用场景,比如『查找附近的美食』、『查找某个区域内的车站』等。
这样的查询也变得越来越流行:要找到离当前位置近期的N个场所,如要找到给定经纬度坐标周围近期的咖啡馆。
MongoDB为坐标平面提供了专门的索引。称作:地理空间索引
createIndex创建索引,只是參数不是1或-1,而是"2d"
db.stus.createIndex({"gps" : "2d"})
"gps"必需是某种形式的一对值:一个包括两个元素的数组或是包括两个键的内嵌文档;键值名能够随意。例如以下:
{ "gps" : [ 0, 100 ] }
{ "gps" : { "x" : -30, "y" : 30 } }
{ "gps" : { "latitude" : -180, "longitude" : 180 } }
默认情况下地理空间的范围是-180~180(经纬度)。要想用其他值,能够通过选项指定最大最小值:
db.stus.createIndex({"gps" : "2d"}, {"min" : -1000, "max" : 1000})
这样就创建了一个2000见方的索引。
6.1 地理空间查询方法
使用$near
返回离[40, -73]近期的10个文档
db.map.find({"gps" : {"$near" : [40, -73]}}).limit(10)
或是:
db.runCommand({geoNear : "map", near : [40, -73], num : 10});
6.2 查找指定开头内的文档
即将原来的 $near 换成 $within
$ within形状參数文档
搜索矩形内的文档:使用"$box"
db.map.find({"gps" : {"$ within" : {"$box" : [[10, 20], [15, 30]]}}})
搜索圆形内的文档:使用"$center"
db.map.find({"gps" : {"$ within" : {"$center" : [[12, 25], 5]}}})
6.3 复合地理空间索引
应用常常要找的东西不仅仅是一个地点。比如。用户要找出周围全部的咖啡店或披萨店。将地理空间索引与普通索引组合起来就能够满足这样的需求。
比如。要查询"location"和"desc",就能够这样创建索引:
db.stus.createIndex({"gps" : "2d", "name" : 1})
然后就可能非常快找到近期的咖啡馆了
db.stus.find({"gps" : {"$near" : [10, 19]}, "name" : "咖啡店"}).limit(1)
注意:创建一个关键词组对于用户自己定义查找非常有帮助。
7、唯一索引 (unique index)
保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引
db.stus.createIndex({ name: 1 }, { unique: true })
8、TTL索引
自动过期或删除文档。TTL索引允许你指定一个时间段,文档将在该时间段之后自动删除。这对于处理需要自动清理或过期的数据非常有用,例如会话数据、日志、缓存等
# 在插入时的3600秒(1小时)之后自动过期并被删除文档
db.stus.createIndex({ "name": 1 }, { expireAfterSeconds: 3600 })
9、部分索引 (partial index)
只针对符合某个特定条件的文档建立索引,这可以减小索引的大小,提高性能,特别是在处理大型数据集合时。
# name:创建索引的字段
# partialFilterExpression 是一个用于筛选文档的条件,只有 "anotherField" 等于 "someValue" 的文档才会被索引。
db.stus.createIndex({ "name": 1 },{partialFilterExpression: { "anotherField": { $eq: "someValue" } }})
10、稀疏索引(sparse index)
只针对存在索引字段的文档建立索引,用于处理包含大量空或缺失字段的文档的集合,这可以减小索引大小,提高性能,适用于包含空字段的文档
db.yourCollection.createIndex({ "yourField": 1 },{ sparse: true })
三、执行查询计划
一)explain()
索引已经建立了,但查询还是很慢怎么破?这时就得深入的分析下索引的使用情况了,可通过查看下详细的查询计划来决定如何优化。通过执行计划可以看出如下问题
根据某个/些字段查询,但没有建立索引
根据某个/些字段查询,但建立了多个索引,执行查询时没有使用预期的索引。
建立索引前,db.person.find( {age: 18} )语句执行COLLSCAN,即全表扫描。
db.person.find({age: 18}).explain()
{
"queryPlanner" : {
......
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"age" : {
"$eq" : 18
}
},
"direction" : "forward"
},
......
},
......
}
建立索引后,通过查询计划可以看出,先进行IXSCAN,然后FETCH,读取出满足条件的文档。
db.person.find({age: 18}).explain()
{
"queryPlanner" : {
.......
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"age" : 1
},
......
}
},
......
},
......
}
注意事项
既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,数据库在运行时也要消耗资源维护索引,因此索引并不是越多越好。一般两种情况下不建议建索引。
- 第一种情况是表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。
- 另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:Index Selectivity = Cardinality / #T
常见慢查询:
1.不等于和不包含查询
2.通配符在前面的模糊查询, like ‘%xxx’
3.无索引的count 查询 和 排序(复合索引顺序不匹配)
4.多个范围查询(范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引)
5.skip跳过过多的行数(优化方案:我们第一页可以用db.article.find().limit(articles_of_each_page),并记录最后一片文章的_id(或者其他排序值),之后查询db.article.find({_id:{$lt:_id_stored}}).limit(articles_of_each_page)来查找下一页或者类似的,上一页的文章,可以避免大量计数.)
二)执行计划三种模式
1、queryPlanner
db.stus.find({"name": "孙悟空3"}).explain("queryPlanner")
queryPlanner是explain的默认模式,是查询执行计划的初始阶段,并不会去真正进行操作语句的执行,它生成多个可能的查询计划并选择其中一个最有希望的计划,以后在executionStats中执行。这一阶段主要关注索引选择、查询优化和计划选择。
当你需要了解MongoDB如何选择执行查询的计划,以及是否使用了适当的索引时,可以查看queryPlanner输出。
- 1)namespace:操作语句所针对的表
- 2)indexFilterSet:布尔值,该操作是否指定indexFilter
- 3)winningPlan:查询优化器对该query返回的最优执行计划的详细内容
- 4)winningPlan.stage:操作阶段的策略
- 5)winningPlan.inputStage/winningPlan.inputStages:子操作阶段
- 6)winningPlan.inputStage.stage:子操作阶段的策略
- 7)winningPlan.inputStage.keyPattern:扫描的index内容
- 8)winningPlan.inputStage.indexName:winning plan所选用的index
- 9)winningPlan.inputStage.isMultiKey:是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true
- 10)winningPlan.inputStage.direction:此query的查询顺序,与sort有关,sort:1为forward,sort:-1为backward
- 11)winningPlan.inputStage.indexBounds:winningplan所扫描的索引范围
- 12)rejectePlans:其他的执行计划(不会被采用,因为不是最优的。最优的是winningPlan,采用的也是winningPlan)
2、executionStats
db.stus.find({"name": "孙悟空3"}).explain("executionStats")
executionStats 阶段是在实际执行查询操作后生成的,提供了查询操作的详细性能数据。
当你需要深入了解查询操作的性能状况,包括实际执行时间和资源消耗时,可以查看executionStats输出。
- 1)executionSuccess:是否执行成功
- 2)nReturned:查询的返回条数
- 3)executionTimeMillis:整体执行时间
- 4)totalKeysExamined:索引扫描次数
- 5)totalDocsExamined:document扫描文档数目
- 6)executionStages:执行阶段的全部详情
- 7)executionStages.works
- 8)executionStages.advanced
- 9)executionStages.needTime
- 10)executionStages.needYield:存储层产生锁的次数
- 11)executionStages.isEOF:指定执行阶段是否结束
- 12)executionStages.inputStage.keysExamined:执行过程中扫描索引数据的次数
- 13)executionStages.inputStage.docsExamined:执行阶段扫描文档数据的次数
3、allPlansExecution
该模式是前两种模式的更细化,会包括上述两种模式的所有信息。即按照最佳的执行计划列出统计信息,还会列出一些候选的执行计划。
三)IndexFilter
1、概念
IndexFilter决定了查询优化器对于某一类型的查询将如何使用index,indexFilter仅影响查询优化器对于该类查询可以尝试用哪些index的执行计划分析,查询优化器还是根据分析情况选择最优计划。
2、IndexFilter的创建
可以通过如下命令为某一个collection建立indexFilter
db.runCommand(
{
planCacheSetFilter: <collection>,
query: <query>,
sort: <sort>,
projection: <projection>,
indexes: [ <index1>, <index2>, ...]
}
)
db.runCommand(
{
planCacheSetFilter: "orders",
query: { status: "A" },
indexes: [
{ cust_id: 1, status: 1 },
{ status: 1, order_date: -1 }
]
}
)
以上针对orders表建立了一个indexFilter,indexFilter指定了对于orders表只有status条件(仅对status进行查询,无sort等)的查询的indexes,故下图的查询语句的查询优化器仅仅会从{cust_id:1,status:1}和{status:1,order_date:-1}中进行winning plan的选择
db.orders.find( { status: "D" } )
db.orders.find( { status: "P" } )
3、IndexFilter的列表
可以通过如下命令展示某一个collecton的所有indexFilter
db.runCommand( { planCacheListFilters: <collection> } )
4、IndexFilter的删除
可以通过如下命令对IndexFilter进行删除
db.runCommand(
{
planCacheClearFilters: <collection>,
query: <query pattern>,
sort: <sort specification>,
projection: <projection specification>
}
)
四)Stage
如queryPlanner.winningPlan.stage和queryPlanner.winningPlan.inputStage等。
- COLLSCAN:全表扫描
- IXSCAN:索引扫描
- FETCH:根据索引去检索指定document
- SHARD_MERGE:将各个分片返回数据进行merge
- SORT:表明在内存中进行了排序(与老版本的scanAndOrder:true一致)
- LIMIT:使用limit限制返回数
- SKIP:使用skip进行跳过
- IDHACK:针对_id进行查询
- SHARDING_FILTER:通过mongos对分片数据进行查询
- COUNT:利用db.coll.explain().count()之类进行count运算
- COUNTSCAN:count不使用用Index进行count时的stage返回
- COUNT_SCAN:count使用了Index进行count时的stage返回
- SUBPLA:未使用到索引的$or查询的stage返回
- TEXT:使用全文索引进行查询时候的stage返回
- PROJECTION:限定返回字段时候stage的返回
五)结果分析
1、executionTimeMillis分析
executionStats模式中,有3个执行时间:
- executionTimeMillis:该query的整体查询时间
- executionStages.executionTimeMillis:该查询根据index去检索document获取数据的时间
- executionStages.inputStage.executionTimeMillis:该查询扫描index行所用时间
这三个值越小,该查询的性能最好
2、nReturned、totalKeysExamined与totalDocsExamined分析
这三个返回值分别代表该条查询返回的条目、索引扫描条目和根据索引去检索文档的扫描条目。这些都直观的影响到executionTimeMillis值的大小。
对于一个查询, 我们最理想的状态是:
- 1)索引覆盖
nReturned=totalKeysExamined & totalDocsExamined=0
即仅仅使用到了index,无需文档扫描,这是最理想状态。 - 2)正常使用索引
nReturned=totalKeysExamined=totalDocsExamined
即正常index利用,无多余index扫描与文档扫描。
如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned=totalDocsExamined的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。
3、Stage状态分析
Stage类型会影响到totalKeysExamined和totalDocsExamined,从而影响executionTimeMillis值的大小。
3.1 对于普通查询,我们最希望看到的组合有这些
- Fetch+IDHACK
- Fetch+ixscan
- Limit+(Fetch+ixscan)
- PROJECTION+ixscan
- SHARDING_FILTER+ixscan
3.2 不希望看到包含如下的stage
- COLLSCAN(全表扫)
- SORT(使用sort但是无index)
- 不合理的SKIP
- SUBPLA(未用到index的$or)
3.3 对于count查询,希望看到的有
- COUNT_SCAN
不希望看到的有: - COUNTSCAN
当查询覆盖精确匹配,范围查询与排序的时候,{精确匹配字段,排序字段,范围查询字段}这样的索引排序会更为高效。
4、执行计划说明(截图)
当我们查询条件为两个时,如:
db.commits.find({"eId":"5913cd0ee727e70007a109f2","sId":"5913c700434a8b00077256fe"}).explain("executionStats")
因前面已经给eId创建了索引,所以本次查询范围从18248条记录,降到了80条记录,而sId没有创建索引,所有我们从80条记录里得到了符合eId,sId这两个条件的38条记录,那么我们现在给sId也创建索引
db.commits.createIndex({sId:1})
当查询的两个条件sId,eId都创建了索引后,再执行
db.commits.find({"eId":"5913cd0ee727e70007a109f2","sId":"5913c700434a8b00077256fe"}).explain("executionStats")
四、慢查询
一)获取当前监控状态
db.getProfilingStatus()
二)设置监控
db.setProfilingLevel(level,options)
// Level:0:关闭日志收集 1:收集大于slowms的日志 2:收集所有日志
// Options:若是整数, 则赋值给slowms;若是对象 , 则是slowms : 阈值 Default: 100, 单位毫秒(milliseconds),sampleRate : 采样率 Default: 1.0 , eg. 1 记录所有慢查询 , 0.5 记录50%的慢查询
三)获取慢查询
// 按执行时间排序获取前20条
db.system.profile.find().sort( { millis : -1 } ).limit(20).pretty();
// 获取最近20条执行数据
db.system.profile.find().limit(20).sort( { ts : -1 } ).pretty();
// 获取大于100ms的查询的20条数据
db.system.profile.find( { millis : { $gt : 100 } } ).limit(20).pretty()
// 获取时间范围段
db.system.profile.find({ts : {$gt: new ISODate("2020-06-09T03:00:00Z"),$lt: new ISODate("2020-06-09T03:40:00Z")}}).limit(20).pretty()
注意: 分析可能会影响性能并与系统日志共享设置。在生产部署上配置和启用探查器之前,请仔细考虑所有性能和安全隐患。
四)监控技巧
在测试/生产环境中,一个数据库可能会被多个应用使用,生成的慢查询都会记录在一起,若能清晰区分各自应用都慢查询,能准确区分到各自应用进行调优/调试
在这里使用是appName区分各自应用,跟SQL Server的Application Name是一样道理。
假设现在我想监控我本地调试的所有Mongodb查询
1、写入超过200毫秒的查询
db.setProfilingLevel(0) # 禁止日志记录
db.system.profile.drop() # 删除system.profile集合
db.createCollection("system.profile", { capped: true, size: 1024 * 1024 * 4 }) # 创建一个新system.profile集合,创建一个4MB大小的日志收集集合
db.setProfilingLevel(1, 200) # 重新启用分析
2、修改连接字符串
mongodb://<username>:<password>@127.0.0.1:27017/<database>?appName=wilson-debug
3、查询监控
db.system.profile.find({ appName: 'wilson-debug',
ts: { $gt: new Date(ISODate().getTime() - 1000 * 60) } }) //一分钟内执行的语句
.sort({ ts: 1})
.limit(20)
.pretty()
五、正确建立索引
在没有建立索引的情况下,对Mongodb数据表进行查询操作的时候,需要把数据都加载到内存。当数据的数量达到几十万乃至上百万的时候,这样的加载过程会对系统造成较大的冲击,并影响到其他请求的处理过程。
索引是对数据库表中一列或多列的值进行排序的一种结构,建立索引以后,对索引字段进行查询时,仅会加载索引数据,并能提高查询速度。
一)建立合适的索引
为每一个查询建立合适的索引。
组合索引是创建的索引由多个字段组成,例如:
db.test.createIndex({"username":1, "age":-1}) #1是按升序排列,-1是按降序排列
交叉索引是每个字段单独建立索引,但是在查询的时候组合查找,例如:
db.test.createIndex({"username":1})
db.test.createIndex({"age":-1})
db.test.find({"username":"kaka", "age": 30})
交叉索引的查询效率较低,在使用时,当查询使用到多个字段的时候,尽量使用组合索引,而不是交叉索引。
二)组合索引的字段排列顺序
当我们的组合索引内容包含匹配条件以及范围条件的时候,比如包含用户名(匹配条件)以及年龄(范围条件),那么匹配条件应该放在范围条件之前。
比如需要查询:
db.test.find({"username":"kaka", "age": {$gt: 10}})
那么组合索引应该这样创建:
db.test.createIndex({"username":1, "age":-1})
三)查询时尽可能仅查询出索引字段
有时候仅需要查询少部分的字段内容,而且这部分内容刚好都建立了索引,那么尽可能只查询出这些索引内容,需要用到的字段显式声明(_id字段需要显式忽略!)。因为这些数据需要把原始数据文档从磁盘读入内存,造成一定的损耗。
比如说我们的表有三个字段:
username, age, mobile
索引是这样建立的:
db.test.createIndex({"username":1,"age":-1})
我们仅需要查到某个用户的年龄(age),那可以这样写:
db.test.find({"username":"kaka"}, {"_id":0, "age":1})
注意到上面的语句,我们除了"age":1外,还加了"_id":0,因为默认情况下,_id都是会被一并查询出来的,当不需要_id的时候记得直接忽略,避免不必要的磁盘操作。
四)对现有的数据大表建立索引的时候,采用后台运行方式
在对数据集合建立索引的过程中,数据库会停止该集合的所有读写操作,因此如果建立索引的数据量大,建立过程慢的情况下,建议采用后台运行的方式,避免影响正常业务流程。
db.test.createIndex({"username":1,"age":-1},{"background":true}) #默认情况下background是false。