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

使用場景 / 基礎用途

針對于MongoDB中的慢查詢,我們通常會使用explain命令進行分析醋虏,其結(jié)果包含MongoDB實際執(zhí)行過程寻咒,但返回往往比較冗長,不夠直觀颈嚼。
最近發(fā)現(xiàn)一款小工具毛秘,能更加便捷快速查看explain分析結(jié)果,和大家分享下阻课。
先看效果叫挟,通過小工具,我們可以拿到如下結(jié)果限煞,

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) 使用示例

假設我們有一段查詢?nèi)缦拢?/p>

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

a) 首先署驻,var explainDoc = explainCsr.next(); 看下原始結(jié)果 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) 其次健霹,看下分析工具給出結(jié)果 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) 在實際的執(zhí)行邏輯中秽荞,其實默認去查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

此外,如果想看一些每一步耗時文檔數(shù)量, 可以使用explain('executionStats'),:

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

原始結(jié)果如下,

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

簡單明了阶捆!

原理解析

使用介紹完成,如果感興趣實現(xiàn)钦听,我們看下分析工具是怎么運作的洒试。
以 mongoTuning.quickExplain 舉例

1) 入口代碼

主體調(diào)用函數(shù) 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結(jié)構(gòu),

explainDoc.queryPlanner.winningPlan結(jié)構(gòu)

這里展示MongoDB執(zhí)行過程中stage之間的關系痪宰,
(1) stage "PROJECTION_SIMPLE" 依賴 (2)stage "SORT"叼架,
(2) stage "SORT" inputStage 依賴 (3)stage "SORT_KEY_GENERATOR",
依次類推衣撬,直到(5)stage IXSCAN不再依賴 inputStage 乖订,則結(jié)束。
所以正常的順序是

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

如上則 printInputStage 對應這個遞歸調(diào)用過程具练,代碼如下乍构,有inputStage則繼續(xù)找,直到找到?jīng)]有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);


總結(jié)和延伸

通過分析工具,能更清晰簡潔地拿到explain結(jié)果陵究。
此外眠饮,由于 mongo --shell mongoTuning.js 支持js語法,我們可以根據(jù)需求定制的Mongo Shell命令畔乙,做出Explain類似的分析工具君仆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市牲距,隨后出現(xiàn)的幾起案子返咱,更是在濱河造成了極大的恐慌,老刑警劉巖牍鞠,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咖摹,死亡現(xiàn)場離奇詭異,居然都是意外死亡难述,警方通過查閱死者的電腦和手機萤晴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門吐句,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人店读,你說我怎么就攤上這事嗦枢。” “怎么了屯断?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵文虏,是天一觀的道長。 經(jīng)常有香客問我殖演,道長氧秘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任趴久,我火速辦了婚禮丸相,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彼棍。我一直安慰自己灭忠,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布座硕。 她就那樣靜靜地躺著更舞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坎吻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天宇葱,我揣著相機與錄音瘦真,去河邊找鬼。 笑死黍瞧,一個胖子當著我的面吹牛诸尽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播印颤,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼您机,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了年局?” 一聲冷哼從身側(cè)響起际看,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矢否,沒想到半個月后仲闽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡僵朗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年赖欣,在試婚紗的時候發(fā)現(xiàn)自己被綠了屑彻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡顶吮,死狀恐怖社牲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悴了,我是刑警寧澤搏恤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站让禀,受9級特大地震影響挑社,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巡揍,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一痛阻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腮敌,春花似錦阱当、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捌木,卻和暖如春油坝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刨裆。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工澈圈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帆啃。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓瞬女,卻偏偏與公主長得像,于是被迫代替她去往敵國和親努潘。 傳聞我的和親對象是個殘疾皇子诽偷,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容