mongo 性能分析 explain()
方法
MongoDB 查询分析可以确保我们所建立的索引是否有效,是查询语句性能分析的重要工具。
参考文档
可以设置以下参数:
- queryPlanner
- executionStats
- allPlansExecution
常用的是 executionStats
模式
准备工作
我们现在有一张表,一共 5584702 条数据,数据结构和索引状况如下。 单条结构如下:
{
"_id" : ObjectId("5ff8287c47ec3c1813536e5c"),
"x" : 1, //递增
"name" : "MACLEAN",
"name1" : "MACLEAN",
"name2" : "MACLEAN",
"name3" : "MACLEAN"
}
索引状况如下,目前除了 mongodb 默认 _id 索引外,没有添加任何索引:
> db.testdatas.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.testdatas"
}
]
开始测试
执行如下查询语句:
> db.testdatas.find({}).limit(10).sort({x: 1}).explain("executionStats")
返回结果
{
"queryPlanner" : { // 查询计划信息
"plannerVersion" : 1,
"namespace" : "test.testdatas", // 该 query 所查询的表
"indexFilterSet" : false, // 针对该query是否应用索引
"parsedQuery" : {
},
"winningPlan" : { // 最优执行计划的详细内容
"stage" : "SORT", // 最优执行计划的stage
"sortPattern" : {
"x" : 1
},
"limitAmount" : 10,
"inputStage" : { // 子阶段列表
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "COLLSCAN",
"direction" : "forward"
}
}
},
"rejectedPlans" : [ ] // 其他执行计划
},
"executionStats" : { //查询结果执行情况
"executionSuccess" : true,
"nReturned" : 10, // 符合查询条件的文档数
"executionTimeMillis" : 14920, // 选择查询计划和执行查询所需的总时间(以毫秒为单位)
"totalKeysExamined" : 0, // 索引扫描条目
"totalDocsExamined" : 5584702, // 扫描文档数
"executionStages" : {
"stage" : "SORT",
"nReturned" : 10,
"executionTimeMillisEstimate" : 14071,
"works" : 5584716,
"advanced" : 10,
"needTime" : 5584705,
"needYield" : 0,
"saveState" : 44054,
"restoreState" : 44054,
"isEOF" : 1,
"invalidates" : 0,
"sortPattern" : {
"x" : 1
},
"memUsage" : 1160,
"memLimit" : 33554432,
"limitAmount" : 10,
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"nReturned" : 5584702,
"executionTimeMillisEstimate" : 11874,
"works" : 5584705,
"advanced" : 5584702,
"needTime" : 2,
"needYield" : 0,
"saveState" : 44054,
"restoreState" : 44054,
"isEOF" : 1,
"invalidates" : 0,
"inputStage" : {
"stage" : "COLLSCAN",
"nReturned" : 5584702,
"executionTimeMillisEstimate" : 7506,
"works" : 5584704,
"advanced" : 5584702,
"needTime" : 1,
"needYield" : 0,
"saveState" : 44054,
"restoreState" : 44054,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 5584702
}
}
}
},
"serverInfo" : {
"host" : "localhost",
"port" : 27017,
"version" : "4.0.8",
"gitVersion" : "9b00696ed75f65e1ebc8d635593bed79b290cfbb"
},
"ok" : 1
}
对executionStats返回逐层分析
第一步,分析 executionStats
字段名 | 说明(参考上述查询结果) |
---|---|
executionTimeMillis | 查询语句总耗时。上述查询语句 5584702 条一共耗时 14920 ms |
totalKeysExamined | 索引扫描条目。 上述结果为 0,说明本次查询可能没有用到索引 |
totalDocsExamined | 文档扫描条数。 上述结果为 5584702,说明本次查询把文档全部扫描了一遍 |
nReturned | 返回结果条数。 上述结果返回10条 |
第二步,分析 executionStages
这个主要讨论3个返回项,nReturned、totalKeysExamined、totalDocsExamined,
分别代表该条 查询返回的条目、索引扫描条目、文档扫描条目。 这些都是直观地影响到 executionTimeMillis,我们需要扫描的越少速度越快。
对于一个查询,我们最理想的状态是:
nReturned = totalKeysExamined = totalDocsExamined
第三步,分析 stage 状态
字段名 | 说明(参考上述查询结果) |
---|---|
stage | 查询计划类型。上述结果 SORT, 表明在内存中进行了排序 |
inputStage | 用来描述子stage,并且为其父stage提供文档和索引关键字 |
那么又是什么影响到了totalKeysExamined和totalDocsExamined?是stage的类型。类型列举如下:
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
SORT:表明在内存中进行了排序
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的返回
添加索引后执行同样的查询语句
给该集合添加一个 { x: 1 } 索引
> db.testdatas.createIndex({x: 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
执行上文同样的查询语句:
> db.testdatas.find({}).limit(10).sort({x: 1}).explain("executionStats")
返回结果:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.testdatas",
"indexFilterSet" : false,
"parsedQuery" : {
},
"winningPlan" : {
"stage" : "LIMIT",
"limitAmount" : 10,
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"x" : 1
},
"indexName" : "x_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"x" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"x" : [
"[MinKey, MaxKey]"
]
}
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 10,
"executionTimeMillis" : 1,
"totalKeysExamined" : 10,
"totalDocsExamined" : 10,
"executionStages" : {
"stage" : "LIMIT",
"nReturned" : 10,
"executionTimeMillisEstimate" : 0,
"works" : 11,
"advanced" : 10,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"limitAmount" : 10,
"inputStage" : {
"stage" : "FETCH",
"nReturned" : 10,
"executionTimeMillisEstimate" : 0,
"works" : 10,
"advanced" : 10,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 0,
"invalidates" : 0,
"docsExamined" : 10,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 10,
"executionTimeMillisEstimate" : 0,
"works" : 10,
"advanced" : 10,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 0,
"invalidates" : 0,
"keyPattern" : {
"x" : 1
},
"indexName" : "x_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"x" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"x" : [
"[MinKey, MaxKey]"
]
},
"keysExamined" : 10,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
},
"serverInfo" : {
"host" : "localhost",
"port" : 27017,
"version" : "4.0.8",
"gitVersion" : "9b00696ed75f65e1ebc8d635593bed79b290cfbb"
},
"ok" : 1
}
加索引后结果分析
第一步,分析 executionStats
executionTimeMillis : 查询总耗时 1ms
totalKeysExamined : 索引扫描条目 10
totalDocsExamined : 文档扫描条目 10
第二步,分析 executionStages
nReturned = 10
totalKeysExamined = 10
totalDocsExamined = 10
目前已达到理想的状态:
nReturned = totalKeysExamined = totalDocsExamined
第三步,分析 stage 状态
从结果看 stage 组合为: Limit+(Fetch+ixscan)
良好的查询组合
对于一般查询,建议stage组合(查询的时候尽可能用上索引):
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FITER+ixscan
COUNT_SCAN