天天看點

MongoDB-SQL優化

一、MongoDB查詢優化器

1、MongoDB查詢優化器

1)MongoDB查詢優化器會選擇最優的一條執行計劃來執行SQL。

2)查詢優化器會緩存那些有多條可用索引的SQL的執行計劃條目

2、查詢優化器原理

1)對于每個SQL,查詢優化器會先在在執行計劃緩存中查找執行計劃

2)如果沒有比對到相應的執行計劃,查詢優化器會生成備選執行計劃,并評估他們各自的消耗,選擇中最佳執行計劃,并将23)這些執行計劃放入緩存中

3)MongoDB根據最優執行計劃傳回結果

4)如果比對到可用的執行計劃,MongoDB會通過replanning的機制再次評估目前執行計劃的性能

5)如果評估成功,使用該執行計劃傳回結果

6)如果評估失敗,查詢優化器重複2)操作,最終選擇最優執行計劃傳回結果

MongoDB-SQL優化

3、執行計劃緩存重新整理機制

1)删除集合或者索引會重新重新整理執行計劃緩存

2)執行計劃緩存在MongoDB重新開機後會失效

3)MongoDB2.6版本之後可以執行db.collection.getPlanCache().clear()手動重新整理執行計劃緩存

二、執行計劃解析

1、文法

方法一:

db.collection.find().explain()

檢視help幫助文檔:

db.collection.explain().help()

方法二:

db.collection.explain().find()

db.collection.explain().find().help()

2、執行計劃的三種模式

queryPlanner Mode:隻會顯示 winning plan 的 queryPlanner,自建MongoDB預設模式

executionStats Mode:隻會顯示 winning plan 的 queryPlanner + executionStats

allPlansExecution Mode:會顯示所有執行計劃的 queryPlanner + executionStats,阿裡雲MongoDB預設模式

不論哪種模式下,檢視一個SQL的執行計劃,都是通過查詢優化器來判斷的,對于所有寫操作,查詢執行計劃隻會限制起操作的消耗,不會執行操作進行變更。

3、Mongodb 執行計劃解析

MongoDB執行計劃主要分為兩個部分:queryPlanner、executionStats

示例集合:

db.asir.find().limit(1).pretty()

{

   "_id" : ObjectId("5d3954a3cd19f9203957cea4"),

   "id" : 0,

   "name" : "sakw",

   "age" : 18,

   "date" : ISODate("2019-07-25T07:05:07.695Z")

}

db.asir.createIndex({age:1})

db.asir.createIndex({name:1,age:1})

示例查詢:

db.asir.find({name:"aa",age:{$gte:28},date:ISODate("2019-07-25T07:05:07.695Z")}).explain("executionStats")

1、queryPlanner

queryPlanner主要有三大部分:parsedQuery、winningPlan、rejectedPlans。

我們關注比較多的是winningPlan,檢視SQL目前執行走了什麼索引

queryPlanner: {
    plannerVersion: 1,
    namespace: "test.asir",        //database.collection
    indexFilterSet: false,           //針對該query是否有indexfilter
    parsedQuery: {             //執行計劃解析
    },
    winningPlan: {             //最終執行計劃
    },
    rejectedPlans: [             //競争執行計劃
    ]
}           
1)parsedQuery - SQL解析

該部分解析了SQL的所有過濾條件

"queryPlanner" : {                                                            
    "plannerVersion" : 1,                                                       
    "namespace" : "test.asir",                                                    //database.collection   
    "indexFilterSet" : false,                                                        //該類查詢是否使用indexfilter   
    "parsedQuery" : {                                                                        //查詢解析                  
        "$and" : [                                                                
            {                                                                       
                "date" : {                                                            
                    "$eq" : ISODate("2019-07-25T07:05:07.695Z")                         
                }                                                                     
            },                                                                      
            {                                                                       
                "name" : {                                                            
                    "$eq" : "aa"                                                        
                }                                                                     
            },                                                                      
            {                                                                       
                "age" : {                                                             
                    "$gte" : 28                                                         
                }                                                                     
            }                                                                       
        ]                                                                         
    },                                                                                                             
2)winningPlan - SQL最終選擇的執行計劃

winningPlan可以分三部分來看:stage、filter、inputStage

"winningPlan" : {                                                                             //最終選擇執行計劃                               
        "stage" : "FETCH",                                     //通過索引檢索得到記錄再次進行文檔檢索                     
        "filter" : {                                                                                    
            "date" : {                                           //文檔檢索條件                                 
                "$eq" : ISODate("2019-07-25T07:05:07.695Z")                                                 
            }                                                                                             
        },                                                                                              
        "inputStage" : {                                                                                //子stage                                
            "stage" : "IXSCAN",                                   //索引檢索                                  
            "keyPattern" : {                                      //索引檢索條件                                
                "name" : 1,                                                                                 
                "age" : 1                                                                                   
            },                                                                                            
            "indexName" : "name_1_age_1",                         //索引名字                                  
            "isMultiKey" : false,                                 //是否為多鍵索引                               
            "multiKeyPaths" : {                                                                           
                "name" : [ ],                                                                               
                "age" : [ ]                                                                                 
            },                                                                                            
            "isUnique" : false,                                    //是否為唯一索引                              
            "isSparse" : false,                                    //是否為稀疏索引                              
            "isPartial" : false,                                   //是否為部分索引                              
            "indexVersion" : 2,                                    //索引版本                                 
            "direction" : "forward",                                                                      
            "indexBounds" : {                                      //索引範圍                                 
                "name" : [                                                                                  
                    "[\"aa\", \"aa\"]"                                                                        
                ],                                                                                          
                "age" : [                                                                                   
                    "[28.0, inf.0]"                                                                           
                ]                                                                                           
            }                                                                                             
        }                                                                                               
    },                                                                                                                                                                                                           
3)rejectedPlans - 被淘汰的執行計劃
"rejectedPlans" : []      //競選失敗的執行計劃               

2、executionStats

最好的情況是:nReturned = totalKeysExamined = totalDocsExamined

"executionStats" : {                                                                                                   
    "executionSuccess" : true,                                                                     //是否執行成功                                                
    "nReturned" : 1,                                             //傳回記錄數                                                 
    "executionTimeMillis" : 0,                                   //SQL總執行時間消耗,ms                                         
    "totalKeysExamined" : 11,                                    //索引掃描數                                                 
    "totalDocsExamined" : 11,                                    //文檔掃描數                                                 
    "executionStages" : {                                                                                                
        "stage" : "FETCH",                                         //通過索引檢索得到記錄再次進行文檔掃描的過程                                 
        "filter" : {                                                                                                       
            "date" : {                                                                                                       
                "$eq" : ISODate("2019-07-25T07:05:07.695Z")                                                                    
            }                                                                                                                
        },                                                                                                                 
        "nReturned" : 1,                                                                                                   
        "executionTimeMillisEstimate" : 0,                          //文檔掃描時間消耗,ms                                          
        "works" : 13,                                               //期間所操作的工作單元個數                                         
        "advanced" : 1,                                             //優先傳回給父stage的中間結果集中文檔個數                               
        "needTime" : 10,                                                                                                   
        "needYield" : 0,                                            //請求查詢階段暫停處理并産生鎖定的次數                                   
        "saveState" : 0,                                            //查詢階段暫停處理并儲存其目前執行狀态的次數                                
        "restoreState" : 0,                                         //查詢階段恢複已儲存的執行狀态的次數                                    
        "isEOF" : 1,                                                                                                       
        "invalidates" : 0,                                                                                                 
        "docsExamined" : 11,                                                                                               
        "alreadyHasObj" : 0,                                                                                               
        "inputStage" : {                                                                                         //索引檢索階段                                              
            "stage" : "IXSCAN",                                                                                              
            "nReturned" : 11,                                                                                                
            "executionTimeMillisEstimate" : 0,                                                                               
            "works" : 12,                                                                                                    
            "advanced" : 11,                                                                                                 
            "needTime" : 0,                                                                                                  
            "needYield" : 0,                                                                                                 
            "saveState" : 0,                                                                                                 
            "restoreState" : 0,                                                                                              
            "isEOF" : 1,                                                                                                     
            "invalidates" : 0,                                                                                               
            "keyPattern" : {                                                                                                 
                "name" : 1,                                                                                                    
                "age" : 1                                                                                                      
            },                                                                                                               
            "indexName" : "name_1_age_1",                                                                 //使用索引名稱                                            
            "isMultiKey" : false,                                                                                            
            "multiKeyPaths" : {                                                                                              
                "name" : [ ],                                                                                                  
                "age" : [ ]                                                                                                    
            },                                                                                                               
            "isUnique" : false,                                                                                              
            "isSparse" : false,                                                                                              
            "isPartial" : false,                                                                                             
            "indexVersion" : 2,                                                                                              
            "direction" : "forward",                                                                                         
            "indexBounds" : {                                                                                                
                "name" : [                                                                                                     
                    "[\"aa\", \"aa\"]"                                                                                           
                ],                                                                                                             
                "age" : [                                                                                                      
                    "[28.0, inf.0]"                                                                                              
                ]                                                                                                              
            },                                                                                                               
            "keysExamined" : 11,                                                                                             
            "seeks" : 1,                                                                                                     
            "dupsTested" : 0,                                                                                                
            "dupsDropped" : 0,                                                                                               
            "seenInvalidated" : 0                                                                                            
        }                                                                                                                  
    }                                                                                                                    
},                                                                                                                                                              

3、serverInfo 伺服器資訊

阿裡雲MonogoDB執行個體上其實将這個資訊隐藏掉了。

"serverInfo" : {                                           
    "host" : "dbslave2",                                         //主機                       
    "port" : 28002,                                              //端口
    "version" : "4.0.10-5",                                      //版本
    "gitVersion" : "7dab0a3a7b7b40cf71724b5a11eff871f8c3885c"    //MongoDB Server git版本号
},                                                         
           

4、indexFilterSet

indexFilter僅僅決定對于該查詢MongoDB可選擇的索引是由什麼決定的。若indexFilterSet為true,說明該查詢隻能選擇indexFilter設定的一些可選索引,最終選擇使用哪個索引由優化器決定;若indexFilterSet=false,說明該查詢可以選擇該集合所有的索引,最終選擇使用哪個索引由優化器确定。

1)如何設定indexFilter

db.runCommand(

  {

   planCacheSetFilter: <collection>,   //需要建立indexFilter集合

   query: <query>,              //指定哪類查詢使用indexFilter

   sort: <sort>,              //排序條件

   projection: <projection>,        //查詢字段

   indexes: [ <index1>, <index2>, ...]      //indexFilter可使用索引

  }

)

2)如何删除indexFilter

   planCacheClearFilters: <collection>,   //指定集合

   query: <query pattern>,     //指定查詢類别

   sort: <sort specification>,     //排序條件

   projection: <projection specification>    //查詢字段

3)如何檢視一個集合所有的indexFilter

db.runCommand( { planCacheListFilters: <collection> } )

4)示例:

集合資料如下:

db.scores.find()

{ "_id" : ObjectId("523b6e32fb408eea0eec2647"), "userid" : "newbie" }

{ "_id" : ObjectId("523b6e61fb408eea0eec2648"), "userid" : "abby", "score" : 82 }

{ "_id" : ObjectId("523b6e6ffb408eea0eec2649"), "userid" : "nina", "score" : 90 }

{ "_id" : ObjectId("5d303213cd8afaa592e23990"), "userid" : "AAAAAAA", "score" : 43 }

{ "_id" : ObjectId("5d303213cd8afaa592e23991"), "userid" : "BBBBBBB", "score" : 34 }

{ "_id" : ObjectId("5d303213cd8afaa592e23992"), "userid" : "CCCCCCC" }

{ "_id" : ObjectId("5d303213cd8afaa592e23993"), "userid" : "DDDDDDD" }

db.scores.createIndex({userid:1,score:1})

建立indexFilter:

> db.runCommand(
...    {
...       planCacheSetFilter: "scores",
...       query: { userid: "abby" },
...       indexes: [
...          { "userid" : 1, "score" : 1},              
...       ]
...    }
... )
{ "ok" : 1 }           

檢視執行計劃:

> db.scores.find({userid: "abbyc"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.scores",
        "indexFilterSet" : true,                        //表示使用了indexFilter指定的索引
        "parsedQuery" : {
            "userid" : {
                "$eq" : "abbyc"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "userid" : 1,
                    "score" : 1
                },
                "indexName" : "userid_1_score_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "userid" : [ ],
                    "score" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "userid" : [
                        "[\"abbyc\", \"abbyc\"]"
                    ],
                    "score" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "dbslave2",
        "port" : 28002,
        "version" : "4.0.10-5",
        "gitVersion" : "7dab0a3a7b7b40cf71724b5a11eff871f8c3885c"
    },
    "ok" : 1
}           

5)注意點

   當使用index filter的時候,使用hint強制走index filter之外的索引會失效,

4、stage類型

stage的類型:

COLLSCAN:全表掃描

IXSCAN:索引掃描

FETCH:根據索引去檢索指定document

SHARD_MERGE:将各個分片傳回資料進行merge

SORT:表明在記憶體中進行了排序

LIMIT:使用limit限制傳回數

SKIP:使用skip進行跳過

IDHACK:針對_id進行查詢

SHARDING_FILTER:通過mongos對分片資料進行查詢

COUNT:利用db.coll.explain().count()之類進行count運算

COUNTSCAN:count不使用Index進行count時的stage傳回

COUNT_SCAN:count使用了Index進行count時的stage傳回

SUBPLA:未使用到索引的$or查詢的stage傳回

TEXT:使用全文索引進行查詢時候的stage傳回

PROJECTION:限定傳回字段時候stage的傳回

對于普通查詢,我希望看到stage的組合(查詢的時候盡可能用上索引):

Fetch+IDHACK

Fetch+ixscan

Limit+(Fetch+ixscan)

PROJECTION+ixscan

SHARDING_FITER+ixscan

COUNT_SCAN

如下的stage效率比較低下:

COLLSCAN(全表掃描)

SORT(使用sort但是無index)

SUBPLA(未用到index的$or)

COUNTSCAN(不使用index進行count)

三、如何排查MongoDB性能問題

1、對于目前正在發生的情況

1)檢視目前會話情況,抓取正在慢的SQL

db.currentOp()
或者
db.currentOp(
   {
     "active" : true,
     "secs_running" : { "$gt" : 3 },
     "ns" : /^db1\./
   }
)           

重點關注:

client          #請求是由哪個用戶端發起
opid            #操作的opid,可以通過 db.killOp(opid) 直接殺掉會話
secs_running/microsecs_running
                #這個值重點關注,代表請求運作的時間,如果這個值特别大,就得注意了,看看請求是否合理
query/ns:       #這個能看出是對哪個集合正在執行什麼操作
lock*:         #還有一些跟鎖相關的參數           

2)檢視問題SQL執行計劃

db.collection.find().explain()           

2、對于曆史問題

1)檢視慢日志以及運作日志

如何調整慢日志參數:

檢視目前慢日志設定參數
> db.getProfilingStatus()                   //檢視目前慢日志參數狀态
{ "was" : 1, "slowms" : 100 }

動态修改慢日志參數
> db.setProfilingLevel(1,10)                //動态修改參數
{ "was" : 1, "slowms" : 100, "ok" : 1 }
> db.getProfilingStatus()
{ "was" : 1, "slowms" : 10 }           

慢日志參數解釋:

was:  慢日志模式  
    0:不開啟慢日志
    1:開啟慢日志,隻記錄超過一定門檻值的慢SQL
    2:開啟慢日志,記錄所有操作

slowms:慢SQL門檻值,機關為ms
           

檢視慢日志資訊:

> db.system.profile.find().sort({$natrual: -1}) //查詢最近慢日志記錄           
db.collection.find().explain()           

四、讀寫操作的一些優化

1、查詢優化

1)建立合适索引

   1.在選擇性較好的字段建立索引

   2.單列索引不需要考慮升降序,但是複合索引可以根據業務需求建立對應升降序的複合索引

   3.覆寫索引查詢,查詢和過濾的所有字段都在複合索引中

db.inventory.createIndex( { type: 1, item: 1 } )

    db.inventory.find(
    { type: "food", item:/^c/ },
    { item: 1, _id: 0 } )                         //需要強制_id字段不顯示以保證走覆寫索引查詢

           

2)使用limit限定傳回結果,減輕網絡開銷

3)需要哪些字段查詢哪些字段

4)使用hint強制走指定索引

2、寫操作優化

1)集合上的索引會增加該集合寫入/更新操作的資源消耗,适度建立索引

MMAPv1存儲引擎中,當一個update操作所需要的空間超過了原本配置設定的空間時,MMAPv1存儲引會将該文檔移動到磁盤上一個新的位置,并全部将該集合的索引進行更新指向新的文檔位置,整個過程是非常消耗資源的。

從MongoDB 3.0開始,MonogoDB使用 Power of 2 Sized Allocations,保證MongoDB盡量對空間的空間重用,盡量減少重新配置設定空間位置的發生。

2)硬體優化,固态SSD的性能要優于HDDs

3)合理設定journal相關參數

   1.journal日志實作日志預寫功能,開啟journal保證了資料的持久化,但也存在一定的性能消耗

   2.盡量将資料檔案與journal日志檔案放在不同的磁盤喜愛,避免I/O資源争用,提高寫操作能力

   3.j:true參數會增加寫操作的負載,根據業務實際情況合理使用write concern參數

   4.設定合理的commitIntervalMs參數

      減小該參數會減少日志送出的時間間隔、增加寫操作的數量,但是會限制MongoDB寫能力。

      增大該參數會增加日志送出的時間間隔、減少寫操作的數量,但是增加了MongoDB意外當機期間日志沒有落盤的可能。