mongo千萬級資料優化
千萬級資料分頁優化
mongo採用的是單機部署,資料量1千萬,需求是實現分頁面,按照capTime倒敘排列,每頁資料20條
-
skip+limit 這是最傳統的資料查詢方式,
db.getCollection('CapMotor').find().skip(9000000).sort({'capTime':1}).limit(20);
skip後面是pageSize*pageIndex,limit後是pageSize 這種方式在資料量是百萬的時候,還湊合著用,但是千萬以後,就不可用了,我這邊跟蹤的查詢時間是9s左右,明顯不行,影響效率,建議採用第2中方法 -
前端傳臨界值id,查詢新增條件,從當前位置往後擷取,取20條
前端在查詢下一頁資料的時候講,當前頁資料的最後一條,capTime這個欄位傳過來,值是:1548482420,並且把該條資料對應的objectid也傳過來,對應值:ObjectId("5bc5ce033b071d2fa84e60f4"),如果是相同的時間,對應的多條資料,要都傳遞過來,mongo查詢的時候,新增條件capTime大於等於1548482420,並且不包含這幾條資料,以下是對應的程式碼
// 資料查詢下一頁時:
db.getCollection('CapMotor').find({
"$and":[
{'capTime' :{ "$gte" :1548482420}},
{'_id' :{"$ne":ObjectId("5bc5ce033b071d2fa84e60f4")}}
]
})
.sort({'capTime':1}).limit(20)
複製程式碼
注:以上是我考慮的按照某個欄位排序,並且該欄位的值有可能會有多個的情況,實際在開發中,該欄位的值有可能是唯一的,對應的程式碼如下
// 資料查詢下一頁時:
db.getCollection('CapMotor').find({'capTime' :{ "$gt" :1539582402} }).sort({'capTime':1}).limit(20)
複製程式碼
第一種方法,前端可以指定調到首頁,尾頁,上下一頁,中間指定頁,但是前提是資料量不過百萬的情況下; 第二種方法執行比較快,但是前端不能跳到指定的頁數,首頁,尾頁,上下一頁這種是可以的;
mongodb 分析器explain
本次是以3.4.15-49-g4ef027f為例,各個版本的執行計劃差異較大
/* 1 */
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "bigdata.faceCapture",
"indexFilterSet" : false,
"parsedQuery" : {
"fcap_id" : {
"$lt" : "fd129550-ced3-11e8-8ea8-1866daf63d9f"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"fcap_id" : 1
},
"indexName" : "fcap_id_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"fcap_id" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"fcap_id" : [
"[\"\", \"fd129550-ced3-11e8-8ea8-1866daf63d9f\")"
]
}
}
},
"rejectedPlans" : []
},
"serverInfo" : {
"host" : "master",
"port" : 27017,
"version" : "3.4.15-49-g4ef027f",
"gitVersion" : "4ef027f98d5c00a0f4e507cbe39a22cab4c7a44c"
},
"ok" : 1.0
}
複製程式碼
重點關注queryPlanner裡的winningPlan即可, explain.queryPlanner.winningPlan.stage:最優執行計劃的stage,這裡返回是FETCH,可以理解為通過返回的index位置去檢索具體的文件(stage有數個模式,將在後文中進行詳解)。
Explain.queryPlanner.winningPlan.inputStage:用來描述子stage,並且為其父stage提供文件和索引關鍵字。
explain.queryPlanner.winningPlan.stage的child stage,此處是IXSCAN,表示進行的是index scanning。
state各個值的解釋如下: COLLSCAN :全表掃描
IXSCAN:索引掃描
FETCH::根據索引去檢索指定document
SHARD_MERGE:各個分片返回資料進行merge
SORT:表明在記憶體中進行了排序(與前期版本的scanAndOrder:true一致)
SORT_MERGE:表明在記憶體中進行了排序後再合併
LIMIT:使用limit限制返回數
SKIP:使用skip進行跳過
IDHACK:針對_id進行查詢
SHARDING_FILTER:通過mongos對分片資料進行查詢
COUNT:利用db.coll.count()之類進行count運算
COUNTSCAN:count不使用用Index進行count時的stage返回
COUNT_SCAN:count使用了Index進行count時的stage返回
SUBPLA:未使用到索引的$or查詢的stage返回
TEXT:使用全文索引進行查詢時候的stage返回
集合faceCapture中有資料1千萬條以上的資料,有複合索引fcap_time、fcap_dcid、person_id
{
"fcap_time" : -1,
"fcap_dcid" : 1,
"person_id" : 1
}
複製程式碼
單行索引fcap_time、fcap_id
{
"fcap_time" : 1
}
複製程式碼
{
"fcap_id" : 1
}
複製程式碼
用關鍵字explain進行sql分析,發現如下問題:
// 走的是複合索引 注意索引的順序,fcap_time" : -1, "fcap_dcid" : 1, "person_id" : 1
db.getCollection('faceCapture').find({'fcap_time' :{ "$gt" :1539745381}}).explain()
// 以下兩個欄位都是複合索引中,未走索引,
db.getCollection('faceCapture').find({'fcap_dcid' :{ "$lt" :"fd129550-ced3-11e8-8ea8-1866daf63d9f"}}).explain()
db.getCollection('faceCapture').find({'person_id' :{ "$lt" :"fd129550-ced3-11e8-8ea8-1866daf63d9f"}}).explain()
// 走的是索引 fcap_id" : -1,
db.getCollection('faceCapture').find({'fcap_id' :{ "$lt" :"fd129550-ced3-11e8-8ea8-1866daf63d9f"}}).explain()
db.getCollection('faceCapture').find({'fcap_id' :{ "$lt" :"fd129550-ced3-11e8-8ea8-1866daf63d9f"}}).count()
複製程式碼
以下有如下結論
mongodb的索引也有最字首原則,類似於mysql中的like關鍵字; 複合索引和單列索引中都有同一列,並且該列是位於複合索引的第一個位置時,預設走的是複合索引; 複合索引的非首位查詢時,預設不走索引;