背景
mongodb 提供了类sql的数据查询及操作方式,同时也包含了聚合操作、索引等多个机制;
按以往的经验,不当的库表操作或索引模式往往会造成许多问题,如查询操作缓慢、数据库吞吐量低下、CPU或磁盘IO飙升等问题。 因此在应用开发过程中,有必要对DB操作进行审视,尤其是关键业务或复杂条件查询。mongodb 提供了explain方法可以让我们 对 DB查询语句进行分析,提前分析潜在的瓶颈。查询计划
mongodb 通过查询计划(QueryPlan)描述一个查询语句的执行过程,而通常一个查询操作可能对应多组查询计划。
这些查询计划通过选举机制产生最优计划,作为最终的执行方案。此外mongodb 还提供了查询计划的缓存机制,如下图:图
Diagram of query planner logic
查询操作被映射到一个查询模型(query shape),模型中会包含条件(predicate)、排序(sort)、投影(projection)的定义;
以查询模型作为Key查找已存在的Plan缓存,在找到缓存的下一步仍进一步评估查询性能,若性能评估结果未达标,则 mongodb会淘汰缓存并进入查询计划生成阶段。 每一个计划生成阶段都会包含:- 产生候选计划;
- 评估优选计划;
- 竞选最优计划;
- 创建缓存;
在产生最优计划之后,查询执行器将执行当前计划并产生最终结果。
explain 操作
通过下面的语句,可以对当前查询计划展开分析
db.T_FooData.find({"appId":"s5WrMmrJV_8RBJG17FSVoY995Kga","nodeType":"SENSOR","creationTime":{ $gte : ISODate("2017-08-08T10:34:33.125Z"), $lt : ISODate("2017-08-08T12:34:33.125Z") }}).explain("executionStats")
输出结果
{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db.T_FooData", "indexFilterSet" : false, "parsedQuery" : { "$and" : [ { "appId" : { "$eq" : "s5WrMmrJV_8RBJG17FSVoY995Kga" } }, { "nodeType" : { "$eq" : "SENSOR" } }, { "creationTime" : { "$lt" : ISODate("2017-08-08T12:34:33.125Z") } }, { "creationTime" : { "$gte" : ISODate("2017-08-08T10:34:33.125Z") } } ] }, "winningPlan" : { ... }, "rejectedPlans" : [ ... ], }, "executionStats" : { "executionSuccess" : true, "nReturned" : 62848, "executionTimeMillis" : 3058, "totalKeysExamined" : 1510833, "totalDocsExamined" : 1510833, "executionStages" : { ... } }, "serverInfo" : { "host" : "NB3000W_MongoDB_01", "port" : 50001, "version" : "3.4.7", "gitVersion" : "4249c1d2b5999ebbf1fdf3bc0e0e3b3ff5c0aaf2" }, "ok" : 1, "$gleStats" : { "lastOpTime" : Timestamp(1504498101, 1), "electionId" : ObjectId("7fffffff0000000000000001") }}
结果说明
- queryPlanner 描述当前的查询计划;
- queryPlanner.namespace 描述当前的集合命名空间,{db}.{collectionName}
- queryPlanner.indexFilterSet 是否设置了indexFilter,Filter决定了查询优化器对于某个查询将如何使用索引
- queryPlanner.parsedQuery 解析后的查询信息
- queryPlanner.winningPlan 最优计划
queryPlanner.rejectPlans 拒绝的计划列表
- executionStats 执行过程统计,捕获计划在执行过程中的相关信息
- executionStats.executionSuccess 是否执行成功
- executionStats.nReturned 返回条目数量
- executionStats.executionTimeMilis 执行时间(ms)
- executionStats.totalKeysExamined 索引检测条目
- executionStats.totalDocsExamined 文档检测条目
executionStats.executionStages 执行阶段详情
explain 模式
mongodb 为 explain 操作提供了几种模式:
- queryPlanner 默认的模式,仅进行查询计划分析,无法输出执行过程统计;
- executionStats 执行模式,在查询计划分析后,将执行winningPlan并统计过程信息;
- allPlansExecution 全计划执行模式,将执行所有计划(包括rejectPlans),并返回过程统计信息;executionStats.allPlansExecution 包含了所有计划(除winningPlan之外)的执行过程统计信息
执行计划详解
执行计划将整个过程分解为多个阶段,阶段(stage)以树状结构组织,这点与执行过程是匹配的。
stage 分为多种类型,如下:
阶段 | 描述 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 索引扫描 |
FETCH | 根据索引去检索指定document |
PROJECTION | 限定返回字段 |
SHARD_MERGE | 将各个分片返回数据进行merge |
SORT | 表明在内存中进行了排序 |
LIMIT | 使用limit限制返回数 |
SKIP | 使用skip进行跳过 |
IDHACK | 针对_id进行查询 |
SHARDING_FILTER | 通过mongos对分片数据进行查询 |
COUNT | 利用db.coll.explain().count()之类进行count运算 |
COUNTSCAN | count不使用用Index进行count |
COUNT_SCAN | count使用了Index进行count |
SUBPLA | 未使用到索引的$or查询 |
TEXT | 使用全文索引进行查询 |
winningPlan 样例
"winningPlan" : { "stage" : "FETCH", "filter" : { "$and" : [ { "nodeType" : { "$eq" : "GATEWAY" } }, { "creationTime" : { "$lt" : ISODate("2017-08-08T12:34:33.125Z") } }, { "creationTime" : { "$gte" : ISODate("2017-08-08T10:34:33.125Z") } } ] }, "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "appId" : 1 }, "indexName" : "appId", "isMultiKey" : false, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 1, "direction" : "forward", "indexBounds" : { "appId" : [ "[\"s5WrMmrJV_8RBJG17FSVoY995Kga\", \"s5WrMmrJV_8RBJG17FSVoY995Kga\"]" ] } } },
字段说明
属性 | 描述 |
---|---|
winningPlan.stage | 最优计划stage,FETCH表示根据索引检索文档 |
winningPlan.filter | 最优计划的过滤器,即查询条件 |
winningPlan.inputStage | 最优计划stage的child stage |
winningPlan.inputStage.stage | child stage,此处是IXSCAN,表示进行index scanning |
winningPlan.inputStage.keyPattern | 扫描的索引模式 |
winningPlan.inputStage.indexName | 选用索引名称 |
winningPlan.inputStage.isMultiKey | 是否是Multikey,如果索引建立在array上则为true |
winningPlan.inputStage.isSparse | 是否稀疏索引 |
winningPlan.inputStage.isPartial | 是否分区索引 |
winningPlan.inputStage.direction | 此query的查询顺序,此处是forward,如果用了.sort({w:-1})将显示backward |
winningPlan.inputStage.indexBounds | 所扫描的索引范围 |
过程统计详解
executionStats 样例
"executionStats" : { "executionSuccess" : true, "nReturned" : 62848, "executionTimeMillis" : 3058, "totalKeysExamined" : 1510833, "totalDocsExamined" : 1510833, "executionStages" : { "stage" : "FETCH", "filter" : { "$and" : [ { "nodeType" : { "$eq" : "GATEWAY" } }, { "creationTime" : { "$lt" : ISODate("2017-08-08T12:34:33.125Z") } }, { "creationTime" : { "$gte" : ISODate("2017-08-08T10:34:33.125Z") } } ] }, "nReturned" : 62848, "executionTimeMillisEstimate" : 2765, "works" : 1510834, "advanced" : 62848, "needTime" : 1447985, "needYield" : 0, "saveState" : 11807, "restoreState" : 11807, "isEOF" : 1, "invalidates" : 0, "docsExamined" : 1510833, "alreadyHasObj" : 0, "inputStage" : { "stage" : "IXSCAN", "nReturned" : 1510833, "executionTimeMillisEstimate" : 792, "works" : 1510834, "advanced" : 1510833, "needTime" : 0, "needYield" : 0, "saveState" : 11807, "restoreState" : 11807, "isEOF" : 1, "invalidates" : 0, "keyPattern" : { "appId" : 1 }, "indexName" : "appId", "isMultiKey" : false, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 1, "direction" : "forward", "indexBounds" : { "appId" : [ "[\"s5WrMmrJV_8RBJG17FSVoY995Kga\", \"s5WrMmrJV_8RBJG17FSVoY995Kga\"]" ] }, "keysExamined" : 1510833, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0 } }}
字段说明
属性 | 描述 |
---|---|
executionStats.executionSuccess | 是否执行成功 |
executionStats.nReturned | 返回条目数量 |
executionStats.executionTimeMilis | 执行时间(ms) |
executionStats.totalKeysExamined | 索引检测条目 |
executionStats.totalDocsExamined | 文档检测条目 |
executionStats.executionStages | 执行阶段详情,大部分字段继承于winningPlan.inputStage |
executionStats.executionStages.stage | 执行阶段,FETCH表示根据索引获取文档 |
executionStats.executionStages.nReturned | 阶段返回条目数量 |
executionStats.executionStages.executionTimeMillisEstimate | 阶段执行时间 |
executionStats.executionStages.docsExamined | 阶段中文档检测条目 |
executionStats.executionStages.works | 阶段中扫描任务数 |
executionStats.executionStages.advanced | 阶段中向上提交数量 |
executionStats.executionStages.needTime | 阶段中定位索引位置所需次数 |
executionStats.executionStages.needYield | 阶段中获取锁等待时间 |
executionStats.executionStages.isEOF | 阶段中是否到达流的结束位,对于limit限制符的查询可能为0 |
executionStats.executionStages.inputStage | 执行阶段的子阶段,这里是一个IXSCAN的子过程 |
参考文档
explain 官方说明
关于explain的几种模式
理解mongo 的查询行为
mongo的查询缓存