MongoDB中的查询分析工具可以帮助我们了解查询过程的详细信息,了解查询的每个步骤,校验索引是否符合预期等。
MongoDB查询分析常用工具有:explain() 和 hint()。
一、explain工具
explain 操作提供了查询信息,使用索引及查询统计等,有利于我们对索引的优化。
1 | db.collection.explain() |
接收三个值入参
- queryPlanner:查询计划的选择器,首先进行查询分析,最终选择一个winningPlan,是explain返回的默认层面
- executionStats:为执行统计层面,返回winningPlan的统计结果
- allPlansExecution:为返回所有执行计划的统计,包括rejectedPlan
queryPlanner为我们选择出了winningPlan,而executionStats为我们统计了winningPlan的所有关键数据。
我们在查询优化的时候,主要是使用executionStats值。
1 | "winningPlan" : { |
explain 结果将查询计划以阶段树的形式呈现,每个阶段将其结果(文档或索引键)传递给父节点。中间节点操纵由子节点产生的文档或索引键。根节点是MongoDB从中派生结果集的最后阶段。
如果使用了executionStats入参,我们可以有下面三个结果输出:
- queryPlanner:它详细说明查询优化器选择的计划,并列出拒绝的计划
- executionStats:详细描述了最优计划和被拒绝的计划
- serverInfo:它提供有关MongoDB实例的信息
1、queryPlanner主要参数解析
1 | "queryPlanner" : { |
这里重点关注winningPlan,它是MongoDB最终执行的它认为最优的查询,这里的stage将告诉你是采用了什么索引进行查询。
stage各类型值的意义
- COLLSCAN:全表扫描
- IXSCAN:索引扫描
- FETCH:根据索引去检索指定document
- SHARD_MERGE:各个分片返回数据进行merge
- SORT:表明在内存中进行了排序
- SORT_MERGE:表明在内存中进行了排序后再合并
- LIMIT:使用limit限制返回数
- SKIP:使用skip进行跳过
- IDHACK:针对_id进行查询
- SHARDING_FILTER:通过mongos对分片数据进行查询
- COUNT:利用db.collection.count()之类进行count运算
- COUNTSCAN:count不使用用Index进行count时的stage返回
- COUNT_SCAN:count使用了Index进行count时的stage返回
- SUBPLA:未使用到索引的$or查询的stage返回
- TEXT:使用全文索引进行查询时候的stage返回
2、executionStats主要参数解析
executionStats信息详细说明了最优计划的执行情况。
1 | "executionStats" : { |
这里重点关注nReturned、executionTimeMillis、totalKeysExamined和totalDocsExamined。
理想状态下:
- nReturned应该等于totalKeysExamined而且totalDocsExamined=0,这种情况相当于索引全覆盖,仅扫描索引,无需扫描文档,是最理想状态
- nReturned、totalKeysExamined和totalDocsExamined三者相等,这种情况相当于检查的键数与返回的文档数相匹配,这意味着MongoDB只需检查索引键即可返回结果,MongoDB不必扫描所有文档或者多余文档,这个查询结果是非常高效的。
- 如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned等于totalDocsExamined的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。
3、serverInfo主要参数
1 | "serverInfo" : { |
4、实例分析
无索引查询name包含”其他”的文档集合:
1 | db.collection.find({ |
explain返回结果:
1 | { |
结果说明:无索引下,MongoDB查询使用了全表扫描(COLLSCAN)的方式进行搜索,无索引遍历所以totalKeysExamined=0,遍历了83986个文档,整个执行耗时(executionTimeMillis)1.379秒,最终返回27条结果。
使用索引查询name包含”其他”的文档集合:
1 | db.collection.createIndex({ 'name': 1 }) |
explain返回结果:
1 | { |
结果说明:对name进行索引加持后,MongoDB查询使用了索引扫描(FETCH+IXSCAN)的方式进行搜索,使用索引名为”name_1”的索引,遍历了83986个索引,遍历了27个文档,整个执行耗时(executionTimeMillis)0.124秒,最终返回27条结果,符合我们期望看的查询组合之一。
二、hint工具
1 | db.collection.hint({ key: 1 }) |
虽然MongoDB查询优化器一般工作的很不错,但是也可以使用hint来强制MongoDB使用一个指定的索引,这种方法在某些情形下会提升性能。
hint()接受索引入参,告诉MongoDB使用入参的索引进行查询计划。
比如,有这样一个查询:
1 | db.collection.find({ |
explain返回结果:
1 | { |
结果说明:MongoDB查询使用了(SUBPLAN+FETCH+IXSCAN)的方式进行搜索,遍历了83986个文档,整个执行耗时(executionTimeMillis)0.428秒,最终返回831条结果。
使用hint强制使用指定索引
1 | db.collection.find({ |
explain返回结果:
1 | { |
结果说明:MongoDB查询使用了(FETCH+IXSCAN)的方式进行搜索,与我们预期一致,遍历了831个文档,整个执行耗时(executionTimeMillis)0.428秒,最终返回831条结果。
在使用hint的查询,少了一个SUBPLAN父阶段,相当于减少了未使用到索引的$or查询stage返回。
三、结语
在查询分析中,我们需要关注查询的点:
全表扫描(关键字:COLLSCAN、totalDocsExamined)。当一个操作(如查询、更新、删除等)需要全表扫描时,将非常占用CPU资源。如果这种情况比较频繁,建议对查询的字段建立索引的方式来优化。通过查看totalDocsExamined的值,可以查看到一个查询扫描了多少文档。该值越大,请求所占用的CPU开销越大。
不合理的索引(关键字: IXSCAN、totalKeysExamined)索引不是越多越好,索引过多会影响写入、更新的性能。如果应用偏向于写操作,索引可能会影响性能。通过查看totalKeysExamined字段,可以查看到一个使用了索引的查询,扫描了多少条索引。该值越大,CPU开销越大。如果索引建立的不太合理,或者是匹配的结果很多。这样即使使用索引,查询开销也不会优化很多,执行的速度也会很慢。
大量数据排序(关键字:SORT)当查询请求里包含排序的时候,如果排序无法通过索引满足,MongoDB会在查询结果中进行排序。而排序这个动作将非常消耗CPU资源,这种情况需要对经常排序的字段建立索引的方式进行优化。
最期望看到的查询组合
- FETCH+IDHACK
- FETCH+IXSCAN
- LIMIT+(FETCH+IXSCAN)
- PROJECTION+IXSCAN
最不期望看到的查询组合
- COLLSCAN(全表扫)
- SORT(使用sort但是无index)
- COUNTSCAN(不使用索引进行count)