> show dbs
admin 0.000GB
local 0.000GB
yang 0.615GB
> use yang
switched to db yang
> show collections
system.profile
users
> db.users.find().count()
10000000
这里选取一千万条数据来说明问题,这些数据都是没有索引的,首先我们要做的是找出慢查询。
1.开启慢查询分析器
db.setProfilingLevel(1,300)
第一个参数“1”表示记录慢查询,还可以选择“0”或者“2”,分别代表不记录数据和记录所有读写操作,我们一般会设置成“1”,第二个参数代表慢查询阈值,只有查询执行时间超过300ms才会记录。
2.查询所记录的慢查询
监控结果保存在一个特殊的盖子集合system.profile里,这个集合分配了128kb的空间,要确保监控分析数据不会消耗太多的系统性资源;盖子集合维护了自然的插入顺序,可以使用$natural操作符进行排序,
> db.system.profile.find().sort({'$natural':-1}).limit(5).pretty()
会打印出最耗时的5条查询,在这里我们选取其中的一条来看:
{
"op" : "command",
"ns" : "yang.users",
"command" : {
"explain" : {
"find" : "users", //这里是所查询的集合
//重点在这里,查询条件是 "username" = "dede"
"filter" : {
"username" : "dede"
}
},
"verbosity" : "allPlansExecution"
},
"numYield" : 78375,
"locks" : {
"Global" : {
"acquireCount" : {
"r" : NumberLong(156752)
}
},
"Database" : {
"acquireCount" : {
"r" : NumberLong(78376)
}
},
"Collection" : {
"acquireCount" : {
"r" : NumberLong(78376)
}
}
},
"responseLength" : 848,
"protocol" : "op_command",
"millis" : 6800,
"ts" : ISODate("2018-02-05T10:35:07.576Z"),
"client" : "127.0.0.1",
"appName" : "MongoDB Shell",
"allUsers" : [ ],
"user" : ""
}
3.上面的还不够直观,我们现在构造这个查询并用explain来分析具体慢在了那里:
> db.users.find({"username":"dede"}).explain('executionStats')
explain的入参可选值为:
"queryPlanner" 是默认值,表示仅仅展示执行计划信息;
"executionStats" 表示展示执行计划信息同时展示被选中的执行计划的执行情况信息;
"allPlansExecution" 表示展示执行计划信息,并展示被选中的执行计划的执行情况信息,还展示备选的执行计划的执行情况信息;
我们一般会选用executionStats,打印出来的内容如下:
> db.users.find({"username":"dede"}).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "yang.users",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "dede"
}
},
//执行计划会有多种,这里列出的是胜出的执行计划
"winningPlan" : {
//重点看下面的stage,collscan代表全表扫描,如果命中索引这里应该是ixscan,index scan
"stage" : "COLLSCAN",
"filter" : {
"username" : {
"$eq" : "dede"
}
},
"direction" : "forward"
},
//下面列举的是没有使用的执行计划
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1428802,
"executionTimeMillis" : 7080,
"totalKeysExamined" : 0,
"totalDocsExamined" : 10000000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"username" : {
"$eq" : "dede"
}
},
"nReturned" : 1428802,//查询所返回的条数
"executionTimeMillisEstimate" : 6633,//执行总时间
"works" : 10000002,
"advanced" : 1428802,
"needTime" : 8571199,
"needYield" : 0,
"saveState" : 78368,
"restoreState" : 78368,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 10000000//总共扫描了一千万条数据
}
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27022,
"version" : "3.4.10",
"gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
},
"ok" : 1
}
讲上面的结果简化如下:
queryPlanner(执行计划描述)
winningPlan(被选中的执行计划)
stage(可选项:COLLSCAN 没有走索引;IXSCAN使用了索引)
rejectedPlans(候选的执行计划)
executionStats(执行情况描述)
nReturned (返回的文档个数)
executionTimeMillis(执行时间ms)
totalKeysExamined (检查的索引键值个数)
totalDocsExamined (检查的文档个数)
我们最终的优化目标
1.根据需求建立索引
2.每个查询都要使用索引以提高查询效率, winningPlan. stage 必须为IXSCAN ;
3.追求totalDocsExamined = nReturned
属性名 | 类型 | 说明 |
---|---|---|
background | boolean | 是否后台构建索引,在生产环境中,如果数据量太大,构建索引可能会消耗很长时间,为了不影响业务,可以加上此参数,后台运行同时还会为其他读写操作让路 |
unique | boolean | 是否为唯一索引 |
name | string | 索引名字 |
sparse | boolean | 是否为稀疏索引,索引仅引用具有指定字段的文档。 |
那么mongodb的索引又是怎么建立的呢?
首先,mongodb的索引分为单建索引db.users. createIndex({age:-1});
、复合索引db.users. createIndex({username:1,age:-1,country:1});
、多键索引(在数组的属性上建立索引)db.users. createIndex({favorites.city:1})
,
创建索引的语法如下:
db.collection.createIndex(keys, options)
语法中 Key 值为要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1,也可以指定为hashed(哈希索引)。
语法中options为索引的属性,属性说明见下表;
属性名 | 类型 | 说明 |
---|---|---|
background | boolean | 是否后台构建索引,在生产环境中,如果数据量太大,构建索引可能会消耗很长时间,为了不影响业务,可以加上此参数,后台运行同时还会为其他读写操作让路 |
unique | boolean | 是否为唯一索引 |
name | string | 索引名字 |
sparse | boolean | 是否为稀疏索引,索引仅引用具有指定字段的文档。 |
举例如下:
单键唯一索引:db.users. createIndex({username :1},{unique:true});
单键唯一稀疏索引:db.users. createIndex({username :1},{unique:true,sparse:true});
复合唯一稀疏索引:db.users. createIndex({username:1,age:-1},{unique:true,sparse:true});
创建哈希索引并后台运行:db.users. createIndex({username :'hashed'},{background:true});
删除索引
根据索引名字删除某一个指定索引:db.users.dropIndex("username_1");
删除某集合上所有索引:db.users.dropIndexs();
重建某集合上所有索引:db.users.reIndex();
查询集合上所有索引:db.users.getIndexes();
既然已经知道了如何创建索引,那么我们就给users表的username字段创建索引
> db.users.createIndex({"username":1},{"name":"username_1","sparse":true,"background":true});
看看有没有创建成功
> db.users.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "yang.users"
},
{
"v" : 2,
"key" : {
"username" : 1
},
"name" : "username_1",
"ns" : "yang.users",
"sparse" : true,
"background" : true
}
]
可以看到索引已经创建成功,索引得名字就是username_1,我们再查看一下执行计划
> db.users.find({"username":"dede"}).explain("executionStats")
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "yang.users",
"indexFilterSet" : false,
"parsedQuery" : {
"username" : {
"$eq" : "dede"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"username" : 1
},
"indexName" : "username_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"username" : [ ]
},
"isUnique" : false,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"username" : [
"[\"dede\", \"dede\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1428802,
"executionTimeMillis" : 4399,
"totalKeysExamined" : 1428802,
"totalDocsExamined" : 1428802,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1428802,
"executionTimeMillisEstimate" : 4002,
"works" : 1428803,
"advanced" : 1428802,
"needTime" : 0,
"needYield" : 0,
"saveState" : 11308,
"restoreState" : 11308,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1428802,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1428802,
"executionTimeMillisEstimate" : 765,
"works" : 1428803,
"advanced" : 1428802,
"needTime" : 0,
"needYield" : 0,
"saveState" : 11308,
"restoreState" : 11308,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"username" : 1
},
"indexName" : "username_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"username" : [ ]
},
"isUnique" : false,
"isSparse" : true,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"username" : [
"[\"dede\", \"dede\"]"
]
},
"keysExamined" : 1428802,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27022,
"version" : "3.4.10",
"gitVersion" : "078f28920cb24de0dd479b5ea6c66c644f6326e9"
},
"ok" : 1
}
我们可以看到,查询时间从原来的6s变成了765ms,并且执行计划中的stage:IXSCAN,再看看"docsExamined" : 1428802,"nReturned" : 1428802,,两者相等,至此我们完成了mongodb的初步调优,这里只是列举的非常简单的场景,具体的调优还是要看各位的实际情况去分析。