本文记录了mongodb执行计划的各个参数及使用注意。
执行计划
MongoDB Query Plans 官方文档:https://docs.mongodb.com/v3.6/core/query-plans/#query-plans
查询计划逻辑图:
MongoDB 3.0之后,explain的返回与使用方法与之前版本有很多变化。
现版本explain有三种模式:
- queryPlanner(默认模式):MongoDB运行查询优化器对当前的查询进行评估并选择一个最佳的查询计划。
- executionStats:MongoDB运行查询优化器对当前的查询进行评估并选择一个最佳的查询计划进行执行,在执行完毕后返回这个最佳执行计划执行完成时的相关统计信息,对于那些被拒绝的执行计划,不返回其统计信息。
- allPlansExecution:该模式包括上述2种模式的所有信息,即按照最佳的执行计划执行以及列出统计信息,如果存在其他候选计划,也会列出这些候选的执行计划。
比如说 db.trade_order.find().explain()
不填默认queryPlanner模式,可指定
.explain("executionStats")
queryPlanner
并不会去真正进行query语句查询,而是针对query语句进行执行计划分析并选出winning plan。
1 | // db.usertable.find({"w": 1}).explain("queryPlanner") |
上例的执行计划返回结果中,queryPlanner.winningPlan.stage
参数,常用的有:
类型 | 描述 |
---|---|
COLLSCAN | 全表扫描 |
IXSCAN | 索引扫描 |
FETCH | 根据索引检索指定的文档 |
SHARD_MERGE | 将各个分片的返回结果进行merge |
SORT | 表明在内存中进行了排序 |
LIMIT | 使用limit限制返回结果数量 |
SKIP | 使用skip进行跳过 |
IDHACK | 针对_id字段进行查询 |
SHANRDING_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的返回 |
上表中,加粗部分为最常用的。
executionStats
executionStats返回的是最优计划的详细的执行信息。
注意,必须是executionStats或者allPlansExecution模式中,才返回executionStats结果。
1 | // 比之上面的,多了一个executionStats返回 |
执行计划的结果是以阶段树的形式呈现,每个阶段将其结果(文档或者索引键)传递给父节点,叶子节点访问集合或者索引,中间节点操纵由子节点产生的文档或者索引键,最后汇总到根节点。
allPlansExecution
allPlansExecution模式返回了更为详细的执行计划结果,这里不再赘述。
IndexFilter
https://docs.mongodb.com/v3.6/core/query-plans/#index-filters
索引过滤器(IndexFilter)决定了查询优化器对于某一类型的查询将如何使用index,且仅影响指定的查询类型。
1 | // 首先,集合 s4 有三个索引 |
那如果有些应用场景下需要特定索引,而不是用查询优化器帮助我们选择的索引,该怎么办?
1 | // 通过 hint 明确指定索引 |
问题解决了。
但我们还有其他的解决方式,就是提前声明为某一类查询指定特定的索引(索引必须存在),当有这类查询时,自动使用特定索引,或者从指定的几个索引中选择一个,而不是通过hint显式指定,且不影响其他类型的查询,这就用到了IndexFilter。
1 | // 创建IndexFilter |
上面的IndexFilter的意思是,当查询集合s4时,且查询类型是age等于某个值时(只作用于该类型的查询),就从indexes
数组中选择一个索引,或者从多个备选索引中,选择一个最优的索引,当然,你也可以像示例中一样,只写一个索引s4_id1
。
之后再来执行一次:
1 | // 对于查询是 age 等于某个值这种类型的查询,查询优化器都会选择我们指定的IndexFilter |
注意,如果某一类型的查询设定了IndexFilter,那么执行时通过hint指定了其他的index,查询优化器将会忽略hint所设置index,仍然使用indexfilter中设定的查询计划,也就是下面这种情况:
1 | db.s4.explain().find({"age": 18}).hint("s4_id2") // 希望在查询时使用 s4_id2 索引 |
另外,IndexFilter不会影响其他类型的查询,如下面的查询类型,查询优化器还是按照原来的规则选择最优计划:
1 | db.s4.explain().find({"age": {"$gt":18}}) |
IndexFilter的其他操作:
1 | // 查看指定集合中的IndexFilter数组 |
当然,在有些情况下,你删除指定的IndexFilter会失败,终极的解决办法是——重启MongoDB服务。
小结:
- IndexFilter为指定类型的查询提前设置使用某一个或者从多个备选索引中选择指定索引。
- IndexFilter指定的索引必须存在,如果索引不存在——也没事!全表扫描呗!
- IndexFilter的优先级高于hint,这点是在生产中要注意的。
- IndexFilter一定程度上解决了某些问题,但它使用相对麻烦,谨慎使用!