mongodb優(yōu)化查詢【轉(zhuǎn)】

查詢優(yōu)化器

MongoDB的查詢計劃會將多個索引并行去執(zhí)行,最先返回101的結(jié)果就是勝者,其他查詢計劃就會被終止,執(zhí)行優(yōu)勝的查詢計劃
這個查詢計劃將會被緩存较鼓,接下來相同的語句查詢條件都會使用它

  • 何時查詢計劃才會變
  1. 建立索引時
  2. 每執(zhí)行1000次查詢之后,查詢優(yōu)化器就會重新評估查詢計劃
  3. 較大的數(shù)據(jù)波動

explain 使用

db.getCollection('db_name').explain('executionStats').aggregate([....])
// 得到的結(jié)果
{
    "stages" : [ 
        {
            "$cursor" : {
                "query" : {
                    .....
                },
                "fields" : {
                    ......
                },
                "queryPlanner" : {
                    ......
                    },
                    "winningPlan" : {                  // 優(yōu)勝的方案
                        "stage" : "FETCH",
                        "filter" : {
                            "$and" : [ 
                                {
                                    "is_deleted" : {
                                        "$eq" : 0.0
                                    }
                                }
                            ]
                        },
                        "inputStage" : {
                            "stage" : "IXSCAN",
                            "keyPattern" : {
                                "acc_opening_date" : 1
                            },
                            "indexName" : "acc_opening_date_1",
                            "isMultiKey" : false,
                            "multiKeyPaths" : {
                                "acc_opening_date" : []
                            },
                            "isUnique" : false,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 2,
                            "direction" : "forward",
                            "indexBounds" : {
                                "acc_opening_date" : [ 
                                    "[new Date(1493625600000), new Date(1609350406000)]"
                                ]
                            }
                        }
                    },
                    "rejectedPlans" : [    // 拒絕的方案
                        {
                            "stage" : "FETCH",
                            "filter" : {
                                "$and" : [ 
                                    {
                                        "is_deleted" : {
                                            "$eq" : 0.0
                                        }
                                    }, 
                                    {
                                        "acc_opening_date" : {
                                            "$lte" : ISODate("2020-12-30T17:46:46.000Z")
                                        }
                                    }, 
                                    {
                                        "acc_opening_date" : {
                                            "$gte" : ISODate("2017-05-01T08:00:00.000Z")
                                        }
                                    }
                                ]
                            },

                        }, 
                        {
                            "stage" : "FETCH",
                            "filter" : {
                                "$and" : [ 
                                    {
                                        "acc_opening_date" : {
                                            "$lte" : ISODate("2020-12-30T17:46:46.000Z")
                                        }
                                    }, 
                                    {
                                        "acc_opening_date" : {
                                            "$gte" : ISODate("2017-05-01T08:00:00.000Z")
                                        }
                                    }
                                ]
                            },
                            "inputStage" : {
                                "stage" : "IXSCAN",  
                                "keyPattern" : {
                                    "is_deleted" : 1  //  is_deteted顆粒度這么大的索引不應(yīng)該建立违柏,難怪要被拒絕
                                },
                                "indexName" : "is_deleted_1",  
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                    "is_deleted" : []
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                    "is_deleted" : [ 
                                        "[0.0, 0.0]"
                                    ]
                                }
                            }
                        }
                    ]
                },
                "executionStats" : {
                    "executionSuccess" : true,
                    "nReturned" : 1,     // 返回的結(jié)果數(shù)量
                    "executionTimeMillis" : 1,  // 運(yùn)行的時間
                    "totalKeysExamined" : 1,  // 掃描的索引數(shù)量
                    "totalDocsExamined" : 1,  // 掃描的文檔數(shù)量
                    "executionStages" : {
                        "stage" : "FETCH",                           //  step2: 用is_deleted字段從上一階段的結(jié)果中過濾出相應(yīng)結(jié)果
                        "filter" : {
                            "$and" : [ 
                                {
                                    "is_deleted" : {
                                        "$eq" : 0.0
                                    }
                                },
                            ]
                        },
                        "nReturned" : 1,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 3,
                        "advanced" : 1,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 1,
                        "restoreState" : 1,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "docsExamined" : 1,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                            "stage" : "IXSCAN",                        //  step1: 用acc_opening_date字段索引搜索出相應(yīng)結(jié)果
                            "nReturned" : 1,
                            "executionTimeMillisEstimate" : 0,
                            "works" : 2,
                            "advanced" : 1,
                            "needTime" : 0,
                            "needYield" : 0,
                            "saveState" : 1,
                            "restoreState" : 1,
                            "isEOF" : 1,
                            "invalidates" : 0,
                            "keyPattern" : {
                                "acc_opening_date" : 1                                    
                            },
                            "indexName" : "acc_opening_date_1",
                            "isMultiKey" : false,
                            "multiKeyPaths" : {
                                "acc_opening_date" : []
                            },
                            "isUnique" : false,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 2,
                            "direction" : "forward",
                            "indexBounds" : {
                                "acc_opening_date" : [ 
                                    "[new Date(1493625600000), new Date(1609350406000)]"
                                ]
                            },
                            "keysExamined" : 1,
                            "seeks" : 1,
                            "dupsTested" : 0,
                            "dupsDropped" : 0,
                            "seenInvalidated" : 0
                        }
                    }
                }
            }
        }, 
        {
            "$lookup" : {
                "from" : "clients",
                "as" : "clients",
                "localField" : "idp_user_id",
                "foreignField" : "idp_user_id",
                "unwinding" : {
                    "preserveNullAndEmptyArrays" : false
                }
            }
        }, 
        {
            "$project" : {
               ......
        }
    ],
    "ok" : 1.0,
    "operationTime" : Timestamp(1604133360, 3),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1604133360, 3),
        "signature" : {
            "hash" : { "$binary" : "RbWJfLtWiuIthJ5C3oiKbGIt1iY=", "$type" : "00" },
            "keyId" : NumberLong(6854528299859705857)
        }
    }
}

  • 查看方式:嵌套最內(nèi)層往外的順序看,不是從上到下香椎。

原因:
explain 結(jié)果將查詢計劃以階段樹的形式呈現(xiàn)漱竖。
每個階段將其結(jié)果傳遞給父節(jié)點(diǎn),中間節(jié)點(diǎn)操作由子節(jié)點(diǎn)產(chǎn)生的文檔或索引

  • 索引使用情況解讀

stage 主要分為以下幾種:
COLLSACN: 全盤掃描
IXSACN: 索引掃描
FETCH: 根據(jù)前面節(jié)點(diǎn)掃描出的文檔畜伐,進(jìn)一步過濾抓取
SORT: 內(nèi)存進(jìn)行排序
SORT_KEY_GENERATOR: 獲取每一個文檔排序所用的鍵值
LIMIT: 使用limit限制返回數(shù)
SKIP: 使用skip進(jìn)行跳過
IDHACK: 針對_id進(jìn)行查詢
COUNTSCAN: count不使用index進(jìn)行count
COUNT_SCACN: count使用index進(jìn)行count
TEXT: 使用全文索引進(jìn)行查詢
SUBPLA:未使用到索引的$or查詢
PROJECTION:限定返回字段

  • 所以不希望看到explain分析出現(xiàn)如下的stage:

COLLSCAN
SORT
COUNTSCAN
SUBPLA

  • 最好是如下的組合:

FETCH + IXSCAN
FETCH + IDHACK
LIMIT + ( FETCH + IXSCAN)
PROJECTION + IXSCAN
COUNT_SCAN

效率極低的操作符

  1. where和exists:這兩個操作符馍惹,完全不能使用索引。
  2. ne和not: 通常來說取反和不等于,可以使用索引玛界,但是效率極低万矾,不是很有效,往往也會退化成掃描全表慎框。
  3. $nin: 不包含良狈,這個操作符也總是會全表掃描
  4. 對于管道中的索引,也很容易出現(xiàn)意外笨枯,只有在管道最開始時的match sort可以使用到索引薪丁,一旦發(fā)生過project投射,group分組馅精,lookup表關(guān)聯(lián)严嗜,unwind打散等操作后,就完全無法使用索引洲敢。

aggregate優(yōu)化

  1. 我認(rèn)為的準(zhǔn)則是盡可能先縮小文檔大新(例如:match,), 然后再排序(sort, limit,skip)压彭,最后進(jìn)行其他復(fù)雜操作(lookup,project, group,unwind),因為這些操作打散后睦优,完全無法使用索引.
    最佳順序: match +sort + $limit + ...
  2. 千萬別忘了$lookup連表的字段,兩張表一定要建立索引
  • 優(yōu)化案例1:limit提前縮小文檔大小哮塞,減少內(nèi)存計算
// 使用時間: 0.044 S
db.getCollection('clients').explain("executionStats").aggregate([ 
    { '$match': { is_deleted: 0 } },
    { '$sort': { gmt_create: -1 } }, 
    { '$lookup': 
        { from: 'client_infos', 
            localField: 'client_info_ids', 
            foreignField: '_id', as: 'client_infos' }
    }, 
    { '$lookup': 
        { from: 'accounts', 
        localField: 'account_id', 
        foreignField: '_id', as: 'account' } 
    }, 
    { '$unwind': '$account' }, 
    { '$skip': 0 }, 
    { '$limit': 10 }], {})

// 使用時間: 0.021s
db.getCollection('clients').explain("executionStats").aggregate([ 
    { '$match': { is_deleted: 0 } },
    { '$sort': { gmt_create: -1 } }, 
    { '$skip': 0 }, 
    { '$limit': 10 },
    { '$lookup': 
        { from: 'client_infos', 
            localField: 'client_info_ids', 
            foreignField: '_id', as: 'client_infos' }
    }, 
    { '$lookup': 
        { from: 'accounts', 
        localField: 'account_id', 
        foreignField: '_id', as: 'account' } 
    }, 
    { '$unwind': '$account' }, 
    ], {})

  • 優(yōu)化案例2: 轉(zhuǎn)換搜索的主表刨秆,使索引生效
// 使用時間: 3.64s
db.getCollection('clients').explain("executionStats").aggregate([ 
    { '$lookup': 
        { from: 'client_infos', 
            localField: 'client_info_ids', 
            foreignField: '_id', as: 'client_infos' }
    }, 
    { '$match': { 'client_infos.phone': 110 } },
    { '$lookup': 
        {
            from: 'accounts', 
            localField: 'account_id', 
            foreignField: '_id', 
            as: 'account',
        }
    }, 
    { '$unwind': '$account' },
    { '$skip': 0 }, 
    { '$limit': 10 }, 
    ], {})

// 使用時間:0.065s
db.getCollection('client_infos').aggregate([ 
    { '$match': { phone: 110, is_deleted: 0} },
    { '$skip': 0 }, 
    { '$limit': 10 }, 
    { '$lookup': 
        { from: 'clients', 
            localField: '_id', 
            foreignField: 'client_info_ids', as: 'clients' }
    }, 
    { '$unwind': '$clients' },
    { '$lookup': 
        { 
            from: 'accounts', 
            localField: 'clients.account_id', 
            foreignField: '_id', 
            as: 'account',
        }
    }, 
    { '$unwind': '$account' },
    ], {})

索引設(shè)計原則

  • 索引字段顆粒度越小越好

顆粒度為結(jié)果集在原集合中所占的比例
顆粒度小的,例如身份證號等唯一性質(zhì)的忆畅,索引掃描能夠很快定位出位置
相反字段顆粒度大的衡未,例如枚舉尸执,例如布爾值,索引定位出的位置不夠精準(zhǔn)缓醋,到頭來還得大部分掃描如失,因為多了索引掃描,最后速度可能還不如全盤掃描送粱。

  • 字段更新頻率小

索引的缺點(diǎn)之一就是修改時還需要維護(hù)索引褪贵,所以最好選擇字段更新比較小的字段

  • 適當(dāng)冗余設(shè)計

aggregate連表查詢。如果查詢字段在副表中抗俄,就無法使用到索引脆丁,如果這種連表查詢頻率較高,可考慮冗余設(shè)計动雹。如上訴案例2槽卫,可通過冗余phone的字段提高查詢效率,以及增加代碼通用性

  • 索引數(shù)量控制

一個索引的字段超過7胰蝠,8歼培,需考慮合理性

查詢優(yōu)化原則

  • 減少帶寬

按需取字段,避免返回大字段

  • 減少內(nèi)存計算

減少中間存儲茸塞,內(nèi)存計算

  • 減少磁盤IO

增加索引躲庄,避免全盤掃描,優(yōu)化sql

參考文檔:
https://zhuanlan.zhihu.com/p/77971681
https://docs.mongodb.com/manual/core/aggregation-pipeline/#aggregation-pipeline-operators-and-performance
https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/
https://jira.mongodb.org/browse/SERVER-28140
https://stackoverflow.com/questions/59811200/lookup-wont-use-indexes-in-second-match-how-can-we-scale

作者:IT女神_
鏈接:http://www.reibang.com/p/f92919ae6c90

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钾虐,一起剝皮案震驚了整個濱河市噪窘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禾唁,老刑警劉巖效览,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荡短,居然都是意外死亡丐枉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門掘托,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘦锹,“玉大人,你說我怎么就攤上這事闪盔⊥湓海” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵泪掀,是天一觀的道長听绳。 經(jīng)常有香客問我,道長异赫,這世上最難降的妖魔是什么椅挣? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任头岔,我火速辦了婚禮,結(jié)果婚禮上鼠证,老公的妹妹穿的比我還像新娘峡竣。我一直安慰自己,他們只是感情好量九,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布适掰。 她就那樣靜靜地躺著,像睡著了一般荠列。 火紅的嫁衣襯著肌膚如雪类浪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天肌似,我揣著相機(jī)與錄音戚宦,去河邊找鬼。 笑死锈嫩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垦搬。 我是一名探鬼主播呼寸,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼猴贰!你這毒婦竟也來了对雪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤米绕,失蹤者是張志新(化名)和其女友劉穎瑟捣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栅干,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迈套,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碱鳞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桑李。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窿给,靈堂內(nèi)的尸體忽然破棺而出贵白,到底是詐尸還是另有隱情,我是刑警寧澤崩泡,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布禁荒,位于F島的核電站,受9級特大地震影響角撞,放射性物質(zhì)發(fā)生泄漏呛伴。R本人自食惡果不足惜勃痴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磷蜀。 院中可真熱鬧召耘,春花似錦、人聲如沸褐隆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庶弃。三九已至衫贬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歇攻,已是汗流浹背固惯。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缴守,地道東北人葬毫。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像屡穗,于是被迫代替她去往敵國和親贴捡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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

  • 查詢優(yōu)化器 MongoDB的查詢計劃會將多個索引并行去執(zhí)行村砂,最先返回101的結(jié)果就是勝者烂斋,其他查詢計劃就會被終止,...
    IT女神_閱讀 1,585評論 0 1
  • 本文主要記錄近期學(xué)習(xí) MongoDB 的一些內(nèi)容础废,主要參考了官方文檔 https://docs.mongodb.c...
    SheHuan閱讀 799評論 0 1
  • 1. 介紹汛骂、安裝、使用(簡單寫寫评腺,不做詳細(xì)介紹) 1.1 介紹 Mongodb是屬于NoSql的一種數(shù)據(jù)類型帘瞭; M...
    Grace_ji閱讀 1,555評論 0 0
  • 前言:一般查詢可以通過find方法,但如果是比較復(fù)雜的查詢或者數(shù)據(jù)統(tǒng)計的話歇僧,find可能就無能為力了图张,這時也許你需...
    岑吾閱讀 371評論 0 7
  • 聚合操作將多個文檔中的值組合在一起并對數(shù)據(jù)進(jìn)行各種操作以返回計算結(jié)果。MongoDB提供了三種執(zhí)行聚合的方法:聚合...
    戒糖少鹽輕碳水閱讀 646評論 0 1