查询计划
- 通过查询计划我们可以看到很多查询的关键因素,尤其是慢查询,mongodb在数据量很大的时候,很可能就需要针对业务场
景去优化索引了,但是在优化索引之前我们需要查询执行计划,先看我们的查询为什么慢。关于查询计划网上有很多详细的分
析我就不列举了,因为整个计划详情非常多,这一篇文章我只给出计划详情中的几个我们首先要关注的指标,在下一篇索引优
化中结合业务场景给出具体的优化示例。如果想详细了解查询计划的,可以参阅我的参考里面的MongoDB干货系列。
一、查看查询计划
- 在执行mongodb的语句的时候,我们在最后加上explain(“executionStats”),然后就能看到执行计划的输出,执行计划也是
以json格式输出的。命令示例如下:
db.getCollection('multobj').find({"targetType":"face"}).sort({time:-1}).limit(100).explain("executionStats")
- 输出执行计划后,执行计划的内容是很多的,因此我们刚刚开始可能没法全部都去关注,但是有几个点基本上是我们首先需要
关注的,后面我们一一来盘点。
二、查询计划核心点
- 下面为了节约篇幅,我只截取了执行计划中的一小部分,如果对执行计划不熟悉可以先了解,或者直接在执行计划里面搜索关
键字。
2.1 耗时多久?
- 在执行计划中我们可以看到执行耗时,在executionStats的executionTimeMillis字段,展示的就是执行耗时:
"executionStats" : {
"nReturned" : 10,
"executionTimeMillis" : 5,//耗时
"totalKeysExamined" : 10,
"totalDocsExamined" : 10,
2.2 扫描数
- 我们还关心一次查询扫描了多少次记录,比如我们查询10条,那么这次查询扫描的是索引还是文档,扫描的数量和我们查询的数量
相差多少?理想的情况是我们扫描的索引数=扫描的文档数=我们需要查询的数目,在查询计划中就是上面展示的。最佳情况是
nReturned=totalDocsExamined=totalKeysExamined,或者nReturned=totalDocsExamined<totalKeysExamined,因为扫描索引是在内存
中,大一点可以接受,但是totalDocsExamined比nReturned大很多,就需要认真对待这次查询了,数据量大的时候很可能会慢。
"executionStats" : {
"nReturned" : 10, //返回记录条数
"executionTimeMillis" : 5, //耗时
"totalKeysExamined" : 10, //扫描的索引数目
"totalDocsExamined" : 10, //扫描的文档数目
2.3 stage字段
-
我们很多时候查询的时候都会指定排序方式,比如时间,权重类字段等。如下是compass客户端工具给我们展示查询计划的时候,他会把这个几个
字段的结果以图形的方式呈现,也可以看原始的json数据,比较直观。
-
内存排序的弊端是什么?因为数据量大的时候,比如你查询近1月的数据,一个月有几千万的数据,放到内存排序是不可能的。如果并行查询都来占据内存后果可想
而知,而且mongodb内部貌似有32MB的限制。如何查看是否使用了内存排序?我们查看winningPlan.inputStage.stage这个字段的值,如下所示:
"winningPlan" : {
"stage" : "LIMIT",
"limitAmount" : 10,
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
- 这里嵌套了好几层,如果字段的值是SORT,那就说明使用了内存排序,这样的查询需要优化。通常比较多见的是FETCH,IXSCAN,我列举几种常见的如下,尤其注意不要出
现不能接受的:
stage字段值 | 含义 | 备注 |
---|---|---|
FETCH | 根据索引去检索指定document | 可以接受 |
IXSCAN | 索引扫描 | 可以接受 |
COUNT_SCAN | count使用索引进行计数 | 可以接受 |
LIMIT | 使用limit限制返回数 | 可以接受 |
SKIP | skip跳过 | 可以接受 |
PROJECTION | 限定返回字段 | 可以接受 |
SORT | 内存排序 | 不能接受 |
COLLSCAN | 全表扫描 | 不能接受 |
COUNTSCAN | count不使用索引进行计数 | 不能接受 |
2.4 使用了什么索引?
- 我们在数据库中可能建立了很多索引,一次查询引擎会选择一个可用的索引,我们通过查询计划可以看到。我们会看到有
winningPlan和rejectedPlans,在winningPlans里面找到indexBounds,就可以看到执行引擎选择的索引,如下的查询可以看到
使用了targetType+time的组合索引,拒绝了targetType字段的单键索引。
SQL:db.getCollection('multobj').find({targetType:"face"}).sort({time:-1}).limit(10).explain("executionStats")
"winningPlan" : {
"stage" : "LIMIT",
"limitAmount" : 10,
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"targetType" : 1,
"time" : -1
},
"indexName" : "targetType_1_time_-1",
"isMultiKey" : false,
"multiKeyPaths" : {
"targetType" : [],
"time" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"targetType" : [
"[\"face\", \"face\"]"
],
"time" : [
"[MaxKey, MinKey]"
]
}
}
}
},
"rejectedPlans" : [
{
"stage" : "SORT",
"sortPattern" : {
"time" : -1.0
},
"limitAmount" : 10,
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"targetType" : 1
},
"indexName" : "targetType_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"targetType" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"targetType" : [
"[\"face\", \"face\"]"
]
}
}
}
}
}
]
三、总结
- 本篇文章我们只是简单了列举了mongodb查询时几个关键的参数,并未进行详细的分析,最后的参考文章中已有对查询计划有详细分析
的文章,因为我是按照自己使用中的经验来写,因此未使用到的就不做详细分析了,有需要可以参考对应的文章。在下一篇文章我会针对
一个具体我使用的业务场景来优化索引,并对比前后的查询计划中的不同点。 - 这里推荐2款mongodb的客户端工具,一个是robo.3t,这个工具比较直观,需要自己写sql。另一个是Mongodb Compass,这个工具对分析
执行计划比较好,尤其是新手,他把执行计划里面的几个关键信息都展示出来了,比如各个阶段的耗时,总耗时,使用哪个索引,是否使用
了内存排序等。
- 如上图中给出了查询的几个关键信息,以及每个阶段的耗时,比如limit,fetch,ixscan等,detail里面有详细的信息,也可以查看原始的json,
对于查询计划不熟悉的时候,可以较为傻瓜式的根据这些信息来看查询的情况是否乐观。