1、explain 函数
3.0 前后版本的explain差别很大,因为版本既然已经升级就没必要去研究低版本的了。
explain 我们叫做查询优化器,mongo 与其他数据库比如MySQL还是有所不同,所谓不同不是结果不同,而是过程不同。那么mongo的优化器确定最优查询线路的过程是怎么样的呢?
1)如果一个索引能够精确匹配一个查询,那么就确定使用这个索引,同时缓存起来,下次直接使用
2)如果有多个索引,那么mongo 分别并行使用这些索引去检索数据,最早返回 100 个结果的就是最优的,同时缓存起来,下次直接使用
3)如果有重建索引、结果集发生变化、已经执行过1000次的查询的任何一种情况,mongo都会清空之前的缓存,重新使用 1 、2 来确定新的最优索引。
看着这个过程,肯定和MySQL的不一致,MySQL是每次都对SQL进行分析查找最优索引,而且他不是真正的去获取数据。
2、语法
1)db.getCollection('examClassStat').find({schoolId:145}).explain(str);
str = null 或 无参,只会把最基础的 queryPlanner 显示出来,当然有附带的 serverInfo 信息
str = queryPlanner,和第一种情况完全一致
str = executionStats,除了 queryPlanner 信息,还有 executionStats 详细信息
str = allPlansExecution,会显示 queryPlanner 、executionStats、allPlansExecution 三部分
3、注意事项
1)一般情况下直接使用无参的 explain 就 OK 了,因为简单看一下是否使用索引就OK,真正说发现很慢的时候才会一步步深入的去获取信息。
2)主要是 explain 返回的两个对象要分清轻重,其一就是 winningPlan.stage 这个表示最后一阶段执行的类型;其二就是 winningPlan.inputStage.stage 表示第一阶段执行的类型;那么可以这样任务先执行的信息都放在 inputStage 里,最后执行的放在 winningPlan 里。
第一阶段就是真正使用索引地方,因此 winningPlan.inputStage.stage 是关键
第二阶段是指获取文档的地方,即 winningPlan.stage , 这个状态其实意义没那么重要了。
3)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的返回
那么,建议就是第一阶段的 stage 应该是 IXSCAN 等用到索引的类型。
4)除了 stage 外,还有几个你常用的字段
-- 数量上,最优的结果自然是 三者都相等了,而且值小的话就再好不过了
executionStats.nReturned:实际返回总数
executionStats.totalKeysExamined:第一阶段遍历的总数
executionStats.totalDocsExamined:第二阶段遍历的总数
-- 时间上,值越小说明越快
executionStats.executionTimeMillis:实际耗费总时间
executionStats.executionStages.executionTimeMillisEstimate:第二阶段耗费时长
executionStats.executionStages.inputStage.executionTimeMillisEstimate:第一阶段耗费时长
5)通过 explain 分两个阶段获取数据可以得知,如果数据量很少的情况下,那么还是用索引去检索数据的话反而会适得其反,因为使用索引需要先查找索引条目,然后通过索引条目找到对应的文档;但是如果你是全表扫描的话,只需要第二个阶段就能完成。
这个也只是一个特例,不管怎么样,互联网时代数据量总会变大。
另外,想要确定使用哪个索引是可以使用 hint 函数, find().hint("index_name");
4、字段说明
db.getCollection('examClassStat')
.find({schoolId:145},{examBaseId:1,"_id":0}).explain("allPlansExecution")
------输出如下
{
"queryPlanner": {
"plannerVersion": 1,
"namespace": "develop.examClassStat",
"indexFilterSet": false,
"parsedQuery": { /* 解析查询语句 */
"schoolId": {
"$eq": 145
}
},
"winningPlan": {
"stage": "PROJECTION", /* 最后一阶段的执行类型 */
"transformBy": {
"examBaseId": 1,
"_id": 0
},
"inputStage": {
"stage": "COLLSCAN", /* 第一阶段的执行类型 */
"filter": {
"schoolId": {
"$eq": 145
}
},
"direction": "forward" /* 与索引无关,查询方向,backward 则相反 */
}
},
"rejectedPlans": [] /* 非最优而被查询优化器reject的 */
},
"executionStats": {
"executionSuccess": true,
"nReturned": 0, /* 返回结果总数 */
"executionTimeMillis": 0, /* 执行总耗时,毫秒 */
"totalKeysExamined": 0, /* 通过索引命中的总数,或是索引扫描的总数 */
"totalDocsExamined": 849, /* 文档扫描总数 */
"executionStages": { /* 最后一阶段执行的详细信息 */
"stage": "PROJECTION", /* 最后一阶段的执行类型 */
"nReturned": 0,
"executionTimeMillisEstimate": 0, /* totalDocsExamined 的耗时 */
"works": 851,
"advanced": 0,
"needTime": 850,
"needYield": 0,
"saveState": 6,
"restoreState": 6,
"isEOF": 1,
"invalidates": 0,
"transformBy": {
"examBaseId": 1,
"_id": 0
},
"inputStage": { /* 第一阶段执行的详细信息 */
"stage": "COLLSCAN", /* 执行该阶段所用的类型,COLLSCAN 全表扫描 */
"filter": {
"schoolId": {
"$eq": 145
}
},
"nReturned": 0,
"executionTimeMillisEstimate": 0, /* totalKeysExamined 的耗时 */
"works": 851,
"advanced": 0,
"needTime": 850,
"needYield": 0,
"saveState": 6,
"restoreState": 6,
"isEOF": 1,
"invalidates": 0,
"direction": "forward",
"docsExamined": 849
}
},
"allPlansExecution": [] /* 所有索引在并行执行的详细结果 */
},
"serverInfo": {
"version": "3.4.7",
"gitVersion": "cf38c1b8a0a8dca4a11737581beafef4fe120bcd"
},
"ok": 1
}