美文网首页
5分钟 Get `MongoDB Explain` 分析小工具

5分钟 Get `MongoDB Explain` 分析小工具

作者: wn777 | 来源:发表于2022-06-11 11:43 被阅读0次

    使用场景 / 基础用途

    针对于MongoDB中的慢查询,我们通常会使用explain命令进行分析,其结果包含MongoDB实际执行过程,但返回往往比较冗长,不够直观。
    最近发现一款小工具,能更加便捷快速查看explain分析结果,和大家分享下。
    先看效果,通过小工具,我们可以拿到如下结果,

    mongo> mongoTuning.executionStats(explainDoc);
    1      COLLSCAN ( ms:10427 docs:411121) 
    2    SORT_KEY_GENERATOR ( ms:10427)
    3  SORT ( ms:10427)
    4 PROJECTION_SIMPLE ( ms:10428)
    
    Totals: ms: 12016 keys: 0 Docs: 411121
    

    使用方法

    1) 下载小工具

    https://github.com/gharriso/MongoDBPerformanceTuningBook 中 mongoTuning.js,

    2) 使用Mongo Shell的时候,引入
    mongo --shell mongoTuning.js
    
    3) 使用示例

    假设我们有一段查询如下:

    var explainCsr = db.customers.explain()
        .find(
            {
                FirstName: "RUTH",
                LastName: "MARTINEZ",
                Phone: 496523103
            },
            { 
                Address: 1, 
                dob: 1 
        })
        .sort({ dob: 1 });
    

    a) 首先,var explainDoc = explainCsr.next(); 看下原始结果 printjson(explainDoc.queryPlanner.winningPlan);, 如下,

    mongo> printjson(explainDoc.queryPlanner.winningPlan);
    {
        "stage": "PROJECTION_SIMPLE",
        "transformBy": {
            "Address": 1,
            "dob": 1
        },
        "inputStage": {
            "stage": "SORT",
            "sortPattern": {
                "dob": 1
            },
            "inputStage": {
                "stage": "SORT_KEY_GENERATOR",
                "inputStage": {
                    "stage": "FETCH",
                    "filter": {
                        "$and": [ <
                            snip >
                        ]
                    },
                    "inputStage": {
                        "stage": "IXSCAN",
                        "keyPattern": {
                            "Phone": 1
                        },
                        "indexName": "Phone_1",
                        "isMultiKey": false,
                        "multiKeyPaths": {
                            "Phone": []
                        },
                        "isUnique": false,
                        "isSparse": false,
                        "isPartial": false,
                        "indexVersion": 2,
                        "direction": "forward",
                        "indexBounds": {
                            "Phone": [
                                "[496523103.0, 496523103.0]"
                            ]
                        }
                    }
                }
            }
        }
    }
    

    b) 其次,看下分析工具给出结果 mongoTuning.quickExplain(explainDoc), 如下

    Mongo Shell>mongoTuning.quickExplain(explainDoc)
    1          IXSCAN Phone_1
    2        FETCH
    3      SORT_KEY_GENERATOR
    4    SORT
    5  PROJECTION_SIMPLE
    

    是不是更加的简洁清晰。 这里 mongoTuning.quickExplain(explainDoc) 在实际的执行逻辑中,其实默认去查explainDoc.queryPlanner.winningPlan
    mongoTuning.quickExplain(explainDoc) =mongoTuning.quickExplain(explainDoc.queryPlanner.winningPlan) ),
    如果我们想看那些 rejectedPlan, 则可以 mongoTuning.quickExplain(explainDoc.queryPlanner.rejectedPlans[1]), 如下:

    Mongo> mongoTuning.quickExplain (explainDoc.queryPlanner.rejectedPlans[1])
    1            IXSCAN LastNmae_1
    2            IXSCAN Phone_1
    3          AND_SORTED
    4        FETCH
    5      SORT_KEY_GENERTOR
    6    SORT
    7  PROJECTION_SIMPLE
    

    此外,如果想看一些每一步耗时文档数量, 可以使用explain('executionStats'),:

    var explainObj = db.customers.explain('executionStats')
        .find(
            {
                FirstName: "RUTH",
                LastName: "MARTINEZ",
                Phone: 496523103
            },
            { 
                Address: 1, 
                dob: 1 
            }
        )
        .sort(
            { dob: 1 }
        );
    

    原始结果如下,

    mongo> explainDoc.executionStats
    {
        "executionSuccess": true,
        "nReturned": 1,
        "executionTimeMillis": 0,
        "totalKeysExamined": 1,
        "totalDocsExamined": 1,
        "executionStages": {
            "stage": "PROJECTION_SIMPLE",
            "nReturned": 1,
            "executionTimeMillisEstimate": 0,
            "works": 6,
            "advanced": 1,
            "needTime": 3,
            "needYield": 0,
            "saveState": 0,
            "restoreState": 0,
            "isEOF": 1,
            "transformBy": {
                "Address": 1,
                "inputStage": {
                    "stage": "SORT",
                    // Many, many more lines of output
                }
            }
        }
    }
    

    带入到工具中,

    mongo> mongoTuning.executionStats(explainDoc);
    1      COLLSCAN ( ms:10427 docs:411121) 
    2    SORT_KEY_GENERATOR ( ms:10427)
    3  SORT ( ms:10427)
    4 PROJECTION_SIMPLE ( ms:10428)
    
    Totals: ms: 12016 keys: 0 Docs: 411121
    

    简单明了!

    原理解析

    使用介绍完成,如果感兴趣实现,我们看下分析工具是怎么运作的。
    以 mongoTuning.quickExplain 举例

    1) 入口代码

    主体调用函数 prepExplain 和 printInputStage.

    mongoTuning.quickExplain = (inputPlan) => {
        // Takes as input an explain Plan.  Emits a simplified
        // version of that plan
        const explainPlan = mongoTuning.prepExplain(inputPlan);
        let stepNo = 1;
    
        const printSpaces = function (n) {
            let s = '';
            for (let i = 1; i < n; i++) {
                s += ' ';
            }
            return s;
        };
        const printInputStage = function (step, depth) {
            if ('inputStage' in step) {
                printInputStage(step.inputStage, depth + 1);
            }
            if ('inputStages' in step) {
                step.inputStages.forEach((inputStage) => {
                    printInputStage(inputStage, depth + 1);
                });
            }
            if ('indexName' in step) {
                print(stepNo++, printSpaces(depth), step.stage, step.indexName);
            } else {
                print(stepNo++, printSpaces(depth), step.stage);
            }
        };
        printInputStage(explainPlan, 1);
    };
    
    2) prepExplain是为了获取 explainPlan, 代码
    mongoTuning.prepExplain = (explainInput) => {
        // Takes as input explain output in one of the follow formats:
        // A fully explain JSON document, in which case emits winningPlan
        // An explain() cursor in which case, extracts the winningPlan from the cursor
        // A specific plan step in which case just returns that
    
        const keys = Object.keys(explainInput);
        // printjson(keys);
        if (keys.includes('queryPlanner')) {
            // This looks like a top level Explain
            return explainInput.queryPlanner.winningPlan;
        } else if (keys.includes('hasNext')) {
            // This looks like a cursor
            if (explainInput.hasNext()) {
                return mongoTuning.prepExplain(explainInput.next());
            }
            return { ok: 0, error: 'No plan found' };
        } else if (keys.includes('stage')) {
            // This looks like an actual plan
            return explainInput;
        }
        return { ok: 0, error: 'No plan found' };
    };
    

    可以看出,如果我们把之前的 explainDoc 传入, 会默认拿到其 queryPlanner.winningPlan, 就是默认拿到winningPlan

    3) printInputStage

    在看 printInputStage 前,先看下 explainDoc.queryPlanner.winningPlan结构,

    explainDoc.queryPlanner.winningPlan结构

    这里展示MongoDB执行过程中stage之间的关系,
    (1) stage "PROJECTION_SIMPLE" 依赖 (2)stage "SORT",
    (2) stage "SORT" inputStage 依赖 (3)stage "SORT_KEY_GENERATOR",
    依次类推,直到(5)stage IXSCAN不再依赖 inputStage ,则结束。
    所以正常的顺序是

    Mongo Shell>mongoTuning.quickExplain(explainDoc)
    1          IXSCAN Phone_1
    2        FETCH
    3      SORT_KEY_GENERATOR
    4    SORT
    5  PROJECTION_SIMPLE
    

    如上则 printInputStage 对应这个递归调用过程,代码如下,有inputStage则继续找,直到找到没有inputStage的stage,输出step.stage后返回。

    const printInputStage = function (step, depth) {
        if ('inputStage' in step) {
            printInputStage(step.inputStage, depth + 1);
        }
        if ('inputStages' in step) {
            step.inputStages.forEach((inputStage) => {
                printInputStage(inputStage, depth + 1);
            });
        }
        if ('indexName' in step) {
            print(stepNo++, printSpaces(depth), step.stage, step.indexName);
        } else {
            print(stepNo++, printSpaces(depth), step.stage);
        }
    };
    printInputStage(explainPlan, 1);
    

    总结和延伸

    通过分析工具,能更清晰简洁地拿到explain结果。
    此外,由于 mongo --shell mongoTuning.js 支持js语法,我们可以根据需求定制的Mongo Shell命令,做出Explain类似的分析工具。

    相关文章

      网友评论

          本文标题:5分钟 Get `MongoDB Explain` 分析小工具

          本文链接:https://www.haomeiwen.com/subject/yausmrtx.html