DAY16 MongoDB Explain 与 Index 建议

DAY16 MongoDB Explain 与 Index 建议

MongoDB explain - executionStats

这篇主要讲解的是 MongoDB explain 指令的 executionStats 项目。
使用此模式进行 explain,会根据你输入的语法,在 query optimizer 找出最佳的查询计画,并且 实际执行你的语法,无论是查询或者新增删除修改。但是请不用担心,这样做并不会真的去改变任何资料库内容,只是协助、更贴近真实情况下,你的语法执行结果如何。

查询语法在 explain 参数中带入 executionStats,即可。

db.collection.find().explain("executionStats")

实际执行

马上进入重头戏,实际执行的部分。我们沿用了上一篇文章的范例,一样是查询 name 栏位,参数则指令了executionStats

db.collection.find("name":"Devil").explain("executionStats")

  • 我省略固定会出现的queryPlannerserverInfo
  • 如果这个范例後面没有中文解释,代表下面会解释
"executionStats" : {
    "executionSuccess" : true,      // 执行结果
    "nReturned" : 1,                // 回传了几个文件
    "executionTimeMillis" : 0,      // 执行时间
    "totalKeysExamined" : 1,        // 总共扫瞄了几把 key (代表有走在 index 上)
    "totalDocsExamined" : 1,        // 总共检查了几个档案
    "executionStages" : {           // 执行阶段
        "stage" : "FETCH",          // 执行此语法使用的方式,使用 index 查询档案
        "nReturned" : 1,            // 回传了几个档案
        "executionTimeMillisEstimate" : 0,
        "works" : 2,
        "advanced" : 1,
        "needTime" : 0,
        "needYield" : 0,
        "saveState" : 0,
        "restoreState" : 0,
        "isEOF" : 1,
        "invalidates" : 0,
        "docsExamined" : 1,         // 扫描了几个档案
        "alreadyHasObj" : 0,
        "inputStage" : {
            "stage" : "IXSCAN",     // 在执行阶段,先使用了什麽方式查找,这边是使用 index
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "keyPattern" : {
                "name" : 1.0
            },
            "indexName" : "name_1",     // 使用的 index 名称
            "isMultiKey" : false,       // 是否为 multi key,如果是在 array 上建立 index,会是 true
            "multiKeyPaths" : {
                "name" : []
            },
            "isUnique" : false,         // index 属性,这三个可以回头查看我之前 index 文章
            "isSparse" : false,
            "isPartial" : false,
            "indexVersion" : 2,
            "direction" : "forward",    // 排序方向,顺向。如果有 sort -1,就会是 backward
            "indexBounds" : {
                "name" : [
                    "[\"Devil\", \"Devil\"]"
                ]
            },
            "keysExamined" : 1,
            "seeks" : 1,
            "dupsTested" : 0,
            "dupsDropped" : 0,
            "seenInvalidated" : 0
        }
    }
}

比较简单的部分就直接在范例後面注解了。
首先我们看一下这个结构

"executionStats":{
    "executionStages":{
        "inputStage":{
            "stage" : "IXSCAN"
            ...
        }
    }
}

winningPlan 是一个树状的结构,实际上会根据你的查询语法而有一个多个 inputStage,所以你可以看到 inputStages 会变成是复数型态。

例如我们今天分别提 age, name 两个栏位分别新增各一把 index,再使用 or 语法来查询

db.employee.createIndex( { name: 1 } )
db.employee.createIndex( { age: 1 } )

db.employee.find({$or: [{"age":35} ,{"name":"Devil"}]  }).explain("executionStats")

就会出现两个 inputStage,如下:

"winningPlan" : {
    "stage" : "SUBPLAN",
    "inputStage" : {
        "stage" : "FETCH",
        "inputStage" : {
            "stage" : "OR",
            "inputStages" : [
                {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "name" : 1.0
                    },
                    "indexName" : "name_1",
                },
                {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "age" : 1.0
                    },
                    "indexName" : "age_1",
                }
            ]
        }
    }
}

executionStats 格式也是一样的,但因为内容更多,所以这边是先贴 winningPlan 结果。

那如果今天我只有一把 key,但还是用 or 来查询两个栏位,那会变成什麽结果呢?
答案是 1 个 inputStage,因为已经是 COLLSCAN 罗~

避免否定类型运算子

官网特别提到的项目,关於 $or$in 运算子,官方直接告诉你要使用$in

假设你要查询 name 栏位是 "Ada" 或 "Bob",不要使用

  • {$or: [{"name":"Ada"}, {"name":"Bob"}]}
    而是要用
  • {"name":{$in:["Ada", "Bob"]}}
    原因就是效能低落,没有别的。

个人补充:依我的经验,因为看到官方提到这个案例,於是在猜想否定类型的运算子是不是也有相同效能问题,後来经过测试,确实是效能比较差,例如 $nin 这种。(当时是 3 版左右吧,不确定现在怎麽样 5.0.1 了...)


  • works : 2
    这个 works 就连官方文件都着墨得少。 works 可分为多个工作单元 work unit,像是查询 single field index key,或是依照 key 读取回一个文件,或是针对取回文件进行 project 都算是一个工作单元,基本上就是最小工作单位的意思。假设一个好的查询,通常会是 nReturned 的数量 +1,为什麽呢? nReturned 就是你查询返回的文件数量,+1 则代表是查询 index 这件工作,因此整体就是 works = nReturned+1。

  • isEOF : 1,
    执行阶段是否已经读取到结尾的地方。

    • 0 代表尚未
    • 1 代表是
      什麽情况下会是 0 呢?通常是查询语法中带有 limt 的语法,使得该次查询并不会查到结尾。

其实还是有很多栏位是我不太理解的,也许之後有翻到文件会再记录下来。至於没写到的,就应该是连相关文献都找不到了XD

索引建议

  • 建立的索引栏位必须与查询语句有强关联
    通常我在确定需求後,会把所有使用者情境列出,并把查询条件整理起来,这些大概就会是你所需要的索引了。
  • 呈上,最好是 covered queries
  • 即使是排序也需要索引
    用来排序的栏位也需要在索引内,否则会资料库排到崩溃,不对,应该是人会等到崩溃
  • 建立的栏位最好是具有独特性
{field_a:1, field_b:'b01'},
{field_a:1, field_b:'b012'},
{field_a:2, field_b:'b02'},
{field_a:2, field_b:'b022'},
{field_a:2, field_b:'b023'},
{field_a:3, field_b:'b03'}

我们替 field_a 建立索引,假设查询是 {field_a:2},那没什麽问题。

那如果我们查询的是 {field_a:2, field_b:'b023'},那麽 MongoDB 就必须要找遍 3 个文件才能找到正确的文件。

从上面的例子能理解独特性的重要,至於要怎麽解这个问题,使用 compound index 就能处理。

  • 特定条件的查询,替它们建立 partial index
    使用情境上,我们时常会查询某种状态或条件的资料,例如 status = 'ERROR' 的状态资料是我们关注的,时常需要去看,这个情境就很适合 partial index
  • 多用 explain (对,这是建议,很基本也很重要)

本系列文章会同步发表於我个人的部落格 Pie Note


<<:  Day 2 测试的不同种类

>>:  Day02 - React component 初认识

Day18-选取器应用_串接json档

今天把昨天写的选取器跟显示东西做串接 首先先写一个json,若会用firebase或App Scri...

DAY 21 - 四足战车 (2)

大家好~ 我是五岁~ 今天来画四足战车的草图细节~ 首先来画上半部 依照昨天的外型草稿,进一步认真的...

第30天:终於撑到完赛QQ-JavaScript技术手册阅读笔记

四开四个主题实在太累,JavaScript技术手册阅读笔记这个主题就是读这本书买很久的书,顺便把之前...

第11车厢-table界的神器!DataTables介绍篇(1)

延续上篇的table介绍,原本功能都要自己写,那有没有工具可以直接套用呢?有的!那就是神器Data...

[Python 爬虫这样学,一定是大拇指拉!] DAY28 - 实战演练:集大成 - 自动更新每日个股日成交资讯

自动更新每日个股日成交资讯 结合前几篇所学,我们来做一个可以自动更新日成交资讯的程序吧! Reque...