MongoDB查詢及索引優化

MongoDB查詢與遊標詳解git

    遊標定義:是一種能從數據記錄的結果集中每次提取一條記錄的機制mongodb

    遊標做用:能夠隨意控制最終結果集的返回,如限制返回數量、跳過記錄、按字段排序、設置遊標超時等。shell

    MongoDB中的遊標數據庫

        對於MongoDB每一個查詢默認返回一個遊標,遊標包括定義、打開、讀取、關閉。json

    MongoDB遊標生命週期數組

        遊標聲明 var cursor=db.collection.find({xxx})   (MongoDB單條記錄的最大大小是16M)緩存

        打開遊標 cursor.hasNext()  遊標是否已經迭代到了最後(正在訪問數據庫)併發

        讀取遊標 cursor.Next()   獲取遊標下一個文檔(正在訪問數據庫)dom

        關閉遊標 cursor.close()  一般迭代完畢會自動關閉,也能夠顯示關閉函數

    MongoDB遊標常見方法
        cursor.batchSize(size)   指定遊標從數據庫每次批量獲取文檔的個數限制

        cursor.count()        統計遊標中記錄總數

        cursor.explain(verbosity)   輸出對應的執行計劃

        cursor.forEach()    採用js函數forEach對每一行進行迭代

        cursor.hasNext()    判斷遊標記錄是否已經迭代完畢

        cursor.hint(index)    認爲強制指定優化器的索引選擇

        cursor.limit()     指定遊標返回的最大記錄數

        cursor.maxTimeMS(time)    指定遊標兩次getmore間隔的最大處理時間(毫秒)推薦

        cursor.next()    返回遊標下一條記錄

        cursor.noCursorTimeout()    強制不自動對空閒遊標進行超時時間計算(默認10分鐘)慎用

    MongoDB shell下游標示例 1

        隱式遊標

        db.t2.find() // 默認迭代20次,其後採用it手動迭代

        DBQuery.shellBatchSize = 10    調整遊標每批次返回的記錄數

        顯示遊標

        var cursor = db.test.find()  遊標定義,此時不會正在訪問數據庫

           while (cursor.hasNext()){

                   printjson(cursor.next())}

        或

            db.test.find().forEach(function(e){printjson(e)})  匿名遊標迭代

    MongoDB shell下游標示例 2

遊標方法hint()

db.person.find({age:1,name:"andy"}).explain()

db.person.find({age:1,name:"andy"}).hint("age_1_name_1").explain()

遊標方法maxTimeMS(time)

 DBQuery.shellBatchSize = 100000

db.t2.find({type:3}).maxTimeMS(1)

修改遊標超時時間

          db.adminCommand( { setParameter: 1, cursorTimeoutMillis: 300000 } )   

    MongoDB遊標最佳實踐

        遊標超時時間與batchsize大小需計算好,避免在getmore時發生沒必要要的超時

        若是業務只須要第一批迭代則查詢時可指定singleBatch:True及時關閉遊標

    MongoDB遊標狀態信息

        db.serverStatus().metrics.cursor

MongoDB索引原理及優化

MongoDB索引原理

db.index.find({}).showRecordId()

 

能夠理解爲每條記錄對應的映射地址

    MongoDB索引類型:

        MongoDB提供了對集合任意字段建立索引的全面支持
        默認狀況下全部普通集合都會自動建立_id惟一性索引
        單列索引
        多列索引
        多鍵索引
        文本索引
        2d
        2dsphere
        hash索引
 
            單列索引

 

 

    db.test.createlndex({socre:1},{background:true,name:"xx_index"})
    默認建立索引加庫級別排它鎖,可指定 background爲true避免阻塞,默認索引名詞:字段名_1 /_-1

    使用background:true構建索引過程依然會阻塞(同DB下)db.collection.drop(),repairDatabase等命令

    可執行db.currentOp()查看索引構建進度也可以使用db.killOp()強制中斷索引建立。

    當副本集/分片節點索引建立被強制中斷後可經過指定indexBuildRetry參數控制節點重啓後是否自動重建索引,

    同時只有當索引構建完畢後才能被對應的查詢語句利用

    對單列索引而言,升序和降序都可以用到索引
    db.records.find({score:2})
    db.records.find({ score : {$gt :10}})

 

 

嵌套單列索引:

    db.test.find()

        {"_id": xxxxxxx,"score:100", "location":{contry:"china",city:beijing}}

    在嵌入式文檔上建立索引

     db.test.createIndex({"location":1},{background:true})

        以下查詢會被用到

            db.test.find({location:{contry:"china",city:"beijing"}})

    在嵌入式字段上建立索引

     db.test.createIndex({"location.city":1},{background:true})

        以下查詢會被用到

            db.test.find({"location.city":"beijing"})

            db.test.find({"location.contry":"chna","location.city":"beijing"})

        在嵌入式文檔上執行相應匹配命令時,字段順序和嵌入式文檔必須徹底匹配

            多列索引

db.test2.createIndex(

{"userid" : 1, "score" : -1, " age " :1},{background:true})

索引排序能夠簡記爲:單列索引正反向排序都不受影響,多列索引則是乘以(-1)的排序可使用相同的索引,即1,1和-1,-1可使用相同的索引, -1,1和1,-1可使用相同的索引

 

最左前綴原則

for (var i = 0 ;i<500000;i++){    db.test10.insert({userid:Math.round(Math.random()*40000),score:Math.round(Math.random()*100),age:Math.round(Math.random()*125)});}  

建立索引:db.test10.createindex({"userid":1,"score":-1,"age":1},{background:true})

如下查詢可使用到索引

db.test10.find({userid:1,score:83,age:20});   

db.test10.find({score:83,userid:1});

db.test10.find({age:20,score:83,userid:1});

db.test10.find({age:20,userid:1});

如下查詢不能使用到索引

db.test10.find({score:83,age:20});

db.test10.find({score:83});

db.test10.find({age:20});

db.test10.find({age:20,score:83});

強制索引

db.test.find({userid:28440,score:88,age:118}).hint("userid_1_score_-1_age_1") ;

索引交集

    普通索引跟hash索引的組合

db.test.createIndex({age:1});

db.test.createIndex({age:"hashed"});

db.test.createIndex({score:1});

db.test.createIndex({score:"hashed"});

db.test.find({score:88,age:118}).explain()

是否採用索引交集的標誌:

經過explain()查看執行計劃的時候會出現AND_SORTED 或 AND_HASG階段(stage)  

or與索引
    db.test.find( { $or: [ { score: 88 }, { age: 8 } ] } ).explain()
索引覆蓋
    db.test.find({userid:681,score:33},{_id:0,age:1}).explain(1)
多列索引與排序
db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({userid:1}); //索引能夠優化排序
db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({score:-1}); //索引能夠優化排序
db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({score:1}); //索引能夠優化排序
 db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({age:1}); //索引沒法優化排序
db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({age:-1});//索引沒法優化排序
 db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({userid:1,score:-1}); // 能夠優化排序
db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({userid:1,score:1}); //不能夠優化排序
 db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({userid:1,score:-1,age:1}); //索引能夠優化排序
db.test.find({userid:1,score:{$gt:25}},{_id:0}).sort({userid:1,score:-1,age:-1}); //索引沒法優化排序
 db.test.find({userid:1,score:53,age:{$gt:29}}).sort({userid:1,score:-1,age:1}); //索引能夠優化排序
 db.test.find({userid:1,score:53,age:{$gt:29}}).sort({userid:-1,score:1,age:-1}); //索引能夠優化排序
db.test.find({userid:1,score:53,age:{$gt:29}}).sort({userid:1,score:-1,age:-1});//索引沒法優化排序

            多鍵索引

                    要索引一個包含數組值的字段,MongoDB會爲數組中的每一個元素建立一個索引鍵,這些多鍵索引支持針對數組字段的高效查詢

                    多鍵索引能夠在包含標量值(例如字符串、數字)和嵌套文檔的數組上建立

db.multi_key.createIndex({ratings:1},{background:true})

db.multi_key.insert({ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] })

db.multi_key.insert({ _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] })

db.multi_key.insert({ _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] })

db.multi_key.insert({ _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] })

db.multi_key.insert({ _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] })

          限制:

            不準建立兩個數組組合索引

            不容許建立hash多key索引

            不能指定爲shard key索引

文本索引

    一個集合最多隻能建立一個文本索引

    db.reviews.createlndex({ comment: "text" })

    能夠對多個字段建立文本索引

    db.reviews.createlndex({subject:"text", comment: "text" })    

2dsphere索引

2d索引

Hash索引

    哈希索引經過索引字段的哈希值來維護條目

              支持使用哈希分片鍵的分片,基於哈希的分片使用字段的哈希索引做爲分片鍵來區分分片數據

              使用哈希分片鍵對集合進行分片會致使數據分佈更加隨機和均勻

              哈希索引使用散列函數來計算索引字段的哈希值。哈希索引不支持多鍵(即數組)索引

              建立哈希索引:

                    db.coll.createindex({_id:"hashed"})

               不可建立具備哈希索引字段的複合索引,或者對哈希索引指定惟一約束。可是,能夠在一個字段上同時建立哈希索引和升序/降序(即非哈希)索引

    索引屬性 

        惟一索引

            強制索引字段不含有重複值,默認狀況下MongoDB會爲_id字段建立惟一性索引

            關鍵字:{unique:true}

            惟一性索引建立:

            db.unique1.createIndex({"user_id":1},{unique:true})

            db.unique2.createIndex({"name":1,"birthday":1},{unique:true})

            限制:

                若是集合已經包含違反索引惟一約束的數據,則MongoDB沒法再指定的索引字段上建立惟一索引

                不能在Hash索引上指定惟一約束

            惟一索引對缺失列的處理

                若是文檔在惟一索引中沒有索引字段的值,則索引將爲此文檔存儲null值。因爲惟一的約束,MongoDB將只容許一個缺失索引字段的文檔。

                若是有多個文檔沒有索引字段的至或缺乏索引字段,則在添加惟一索引時將失敗,並報出重複鍵錯誤。

                    惟一性索引爲null的問題:
        部分索引
            部分文檔僅索引符合指定過濾表達式的集合中的文檔。存儲需求更低,性能成本也更低

            對集合的部分文檔進行索引 :db.t.createIndex({ category: 1 },{ partialFilterExpression: { _id: { $gt: 2 } } } ) 

                partialFilterExpression支持的選項:

                    等值表達式(如 field: value或使用$eq操做符)

                    $exists:true  表達式

                    $gt,$gte,$lt,$lte表達式

                    $type 表達式

                    第一層級的$and操做符

 部分索引的使用

 db.t.find({"category":"F array",_id:{$gt:1}}).explain() //沒法利用部分索引

 db.t.find({"category":"F array",_id:{$gt:3}}).explain() //能夠利用部分索引

查詢要使用部分索引則查詢條件必須與索引表達式相同或是其子集

            部分索引與惟一性

                db.users.insert( { username: "david", age: 25 } )

db.users.insert( { username: "amanda", age: 26 } )

db.users.insert( { username: "andy", age: 30 } )

db.users.createIndex({ username: 1 },{ unique: true, partialFilterExpression: { age: { $gte: 20 } } })

        再次插入用戶名爲andy年齡爲13(不在$gte:20的範圍)的文檔

        db.users.insert( { username: "andy", age: 13} ) //插入成功

    限制

        _id索引不能夠是部分索引

        shard key索引不能夠是部分索引

        稀疏索引

            指僅僅包含具備索引字段的文檔,稀疏索引能夠認爲是部分索引的子集

        稀疏索引的建立

db.collection.insert({ y: 1 } );

db.collection.createIndex( { x: 1 }, { sparse: true } );

        稀疏索引與hint

db.collection.find().hint( { x: 1 } ).count();

db.collection.find().hint( { x: 1 } ) ;

        稀疏索引與惟一性

db.collection.createIndex( { z: 1 } , { sparse: true, unique: true } )

db.collection.insert({y:2}) // 正常

db.collection.insert({y:3}) // 正常  

 

        TTL索引

            是指MongoDB能夠在特定的時間或者特定的時間段後自動刪除集合文檔的單列索引
            TTL索引的規定
                TTL索引字段必須是date類型或存儲date類型數值的數組類型
                TTL索引建立後會啓動一個後臺線程 每分鐘啓動進行文檔刪除(注:TTL索引刪除不是嚴格意義上的1分鐘)
                MongoDB不支持組合TTL索引,不然過時特性會被MongoDB忽略

 

         TTL索引在索引字段值超過指定的秒數後過時文檔; 即,到期閾值是索引字段值加上指定的秒數。

     若是字段是數組,而且索引中有多個日期值,則MongoDB使用數組中的最低(即最先)日期值來計算到期閾值。

     若是文檔中的索引字段不是日期或包含日期值的數組,則文檔將不會過時。

        若是文檔不包含索引字段,則文檔不會過時。

        在後臺建立TTL索引時,TTL索引能夠在構建索引時刪除文檔。若是在前臺構建TTL索引,則在索引構建完畢後當即刪除過時文檔 

    TTL索引的建立

  db.exp.insert({lastDate:new Date()})

  db.exp.createIndex({"lastDate":1},{expireAfterSeconds:60})

 在線修改TTL過時時間

  db.runCommand({collMod:"exp",index{keyPattern:{lastDate:1},expireAfterSeconds:120}})            

    查詢數據庫下的全部索引

         db.getCollectionNames().forEach(function(collection) {

           indexes = db[collection].getIndexes();

           print("Indexes for " + collection + ":");

           printjson(indexes);});

   索引統計信息

        db.serverStatus(scale)的統計輸出

            metrics.queryexecutor.scanned:查詢和查詢計劃評估期間掃描的索引項的總數。蓋計數器與explain()輸出中totalkeysexamamed意思相同

            metrics.operation.scanAndOrder:表示沒法使用索引排序的查詢總次數

        db.users.stats(scale)的統計輸出

            totalindexSize:全部索引的總大小。scale參數影響輸出。如一個索引使用前綴壓縮(WiredTiger的默認),則返回的大小爲索引的壓縮大小

            indexSizes:指定集合上每一個現有索引對應的鍵和大小。scale參數影響輸出

        db.stats(scale)的統計輸出

            indexes:在此數據庫中全部集合的索引總數目

            indexSize:在此數據庫中建立的全部索引的總大小。scale參數影響輸出

 

   索引設計原則

        一、每一個查詢原則上都須要建立對應索引

        二、單個索引設計應考慮知足儘可能多的查詢

        三、索引字段選擇及順序須要考慮查詢覆蓋率及選擇性

        四、對於更新及其頻繁的字段上建立索引需慎重

        五、對於數組索引須要慎重考慮將來元素個數

        六、對於超長字符串類型字段上慎用B數索引

        七、併發更新較高的單個集合上不宜建立過多索引

 

MongoDB通用優化建議與實踐

    MongoDB查詢緩存邏輯

       索引選擇基於採樣代價模型,查詢第一次執行若是有多個執行計劃則會根據模型選出最優計劃並緩存

        語句執行會根據執行計劃的表現對緩存進行重用或重建,如屢次迭代都沒有返回足夠的文檔則可能會觸發重構

        當MongoDB建立或刪除索引時,會將對應集合的緩存執行計劃清空並從新選擇

        若是MongoDB從新啓動或關閉,則查詢計劃緩存會被清理而後依據查詢進行重建

    MongoDB查詢緩存操做

db.eof.find({x:1}); db.eof.find({x:3}).sort({y:1});  執行查詢模擬查詢計劃緩存

db.eof.getPlanCache().listQueryShapes();    查詢對應集合的查詢計劃緩存指紋

db.eof.getPlanCache().getPlansByQuery({"x":1},{},{"y":1});  經過指紋查看查詢計劃緩存信息   

            注:查詢指紋或叫形狀是由query條件、sort條件及projection(投影)組成

            db.eof.getPlanCache().clearPlansByQuery({"x":1},{},{"y":1}) 經過指定指紋清空對應查詢計劃緩存

            db.eof.getPlanCache().clear()  清空對應集合全部執行計劃的緩存信息

   查詢計劃詳解

        支持查詢計劃的操做

            aggregate()

            count()

            distinct()

            find()

            group()

            remove()

            update()

            findAndModify()    

    查詢計劃語法:

            一、db.collection.explain(verbosity).<method(...)>  返回遊標mongo shell 默認迭代

            二、db.collection.<method(...)>.explain(verbosity)   返回json文檔化結果

     查詢計劃詳解:

         queryPlanner模式

                     MongoDB經過查詢優化器對查詢評估後選擇一個最佳的查詢計劃(默認模式)

          executionStats模式

            MongoDB經過查詢優化器對查詢進行評估並選擇一個最佳的查詢計劃執行後返回統計信息

            對於寫操做則返回關於更新和刪除操做的統計信息,但並不真正修改數據庫數據

            對於非最優執行計劃不返回對於統計信息

           allPlansExecution模式

                      與上述兩種模式的差異是除返回兩種模式的信息的同時還包含非最優計劃執行的統計信息   

 

ceshi27020_mongo:PRIMARY> db.eof.find({x:2100}).explain()

{   "queryPlanner" : {                        //查詢計劃信息        

                "plannerVersion" : 1,            //查詢計劃版本

                "namespace" : "test.eof",    //查詢集合

                "indexFilterSet" : false,        //查詢過濾器

                "parsedQuery" : {                //查詢具體條件

                        "x" : {

                                "$eq" : 2100

                        }

                },

                "winningPlan" : {                        //最優計劃

                        "stage" : "FETCH",            //獲取文檔階段

                        "inputStage" : {                // 過濾條件

                                "stage" : "IXSCAN",    //索引掃描階段

                                "keyPattern" : {            //要遍歷的索引

                                        "x" : 1

                                },

                                "indexName" : "idx_x",   //索引名稱

                                "isMultiKey" : false,        //是不是多key索引

                                "isUnique" : false,           //是不是惟一索引

                                "isSparse" : false,            //是不是稀疏索引

                                "isPartial" : false,             //是不是部分索引

                                "indexVersion" : 1,           //索引版本號

                                "direction" : "forward",       //索引掃描方向(forward對應1,backward對應-1)

                                "indexBounds" : {                //索引掃描的邊界

                                        "x" : [  "[2100.0, 2100.0]"]  }  }  }, "rejectedPlans" : [ ]  },

        "serverInfo" : {

                "host" : "LeDB-VM-124064213",

                "port" : 27020,

                "version" : "3.2.20",

                "gitVersion" : "a7a144f40b70bfe290906eb33ff2714933544af8"  },  "ok" : 1}     

相關文章
相關標籤/搜索