mongodb查詢操作分析
背景
mongodb 提供了類sql的數據查詢及操作方式,同時也包含了聚合操作、索引等多個機制;
按以往的經驗,不當的庫表操作或索引模式往往會造成許多問題,如查詢操作緩慢、數據庫吞吐量低下、CPU或磁盤IO飆升等問題。
因此在應用開發過程中,有必要對DB操作進行審視,尤其是關鍵業務或復雜條件查詢。mongodb 提供了explain方法可以讓我們
對 DB查詢語句進行分析,提前分析潛在的瓶頸。
查詢計劃
mongodb 通過查詢計劃(QueryPlan)描述一個查詢語句的執行過程,而通常一個查詢操作可能對應多組查詢計劃。
這些查詢計劃通過選舉機制產生最優計劃,作為最終的執行方案。此外mongodb 還提供了查詢計劃的緩存機制,如下圖:
圖 https://docs.mongodb.com/manual/_images/query-planner-diagram.bakedsvg.sv
Diagram of query planner logic
查詢操作被映射到一個查詢模型(query shape),模型中會包含條件(predicate)、排序(sort)、投影(projection)的定義;
以查詢模型作為Key查找已存在的Plan緩存,在找到緩存的下一步仍進一步評估查詢性能,若性能評估結果未達標,則 mongodb會淘汰緩存並進入查詢計劃生成階段。
每一個計劃生成階段都會包含:
- 產生候選計劃;
- 評估優選計劃;
- 競選最優計劃;
- 創建緩存;
在產生最優計劃之後,查詢執行器將執行當前計劃並產生最終結果。
explain 操作
通過下面的語句,可以對當前查詢計劃展開分析
db.T_FooData.find({ "appId":"s5WrMmrJV_8RBJG17FSVoY995Kga", "nodeType":"SENSOR", "creationTime":{ $gte : ISODate("2017-08-08T10:34:33.125Z"), $lt : ISODate("2017-08-08T12:34:33.125Z") } }).explain("executionStats")
輸出結果
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "db.T_FooData",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"appId" : {
"$eq" : "s5WrMmrJV_8RBJG17FSVoY995Kga"
}
},
{
"nodeType" : {
"$eq" : "SENSOR"
}
},
{
"creationTime" : {
"$lt" : ISODate("2017-08-08T12:34:33.125Z")
}
},
{
"creationTime" : {
"$gte" : ISODate("2017-08-08T10:34:33.125Z")
}
}
]
},
"winningPlan" : { ... },
"rejectedPlans" : [ ... ],
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 62848,
"executionTimeMillis" : 3058,
"totalKeysExamined" : 1510833,
"totalDocsExamined" : 1510833,
"executionStages" : { ... }
},
"serverInfo" : {
"host" : "NB3000W_MongoDB_01",
"port" : 50001,
"version" : "3.4.7",
"gitVersion" : "4249c1d2b5999ebbf1fdf3bc0e0e3b3ff5c0aaf2"
},
"ok" : 1,
"$gleStats" : {
"lastOpTime" : Timestamp(1504498101, 1),
"electionId" : ObjectId("7fffffff0000000000000001")
}
}
結果說明
- queryPlanner 描述當前的查詢計劃;
- queryPlanner.namespace 描述當前的集合命名空間,{db}.{collectionName}
- queryPlanner.indexFilterSet 是否設置了indexFilter,Filter決定了查詢優化器對於某個查詢將如何使用索引
- queryPlanner.parsedQuery 解析後的查詢信息
- queryPlanner.winningPlan 最優計劃
queryPlanner.rejectPlans 拒絕的計劃列表
- executionStats 執行過程統計,捕獲計劃在執行過程中的相關信息
- executionStats.executionSuccess 是否執行成功
- executionStats.nReturned 返回條目數量
- executionStats.executionTimeMilis 執行時間(ms)
- executionStats.totalKeysExamined 索引檢測條目
- executionStats.totalDocsExamined 文檔檢測條目
executionStats.executionStages 執行階段詳情
explain 模式
mongodb 為 explain 操作提供了幾種模式:
- queryPlanner 默認的模式,僅進行查詢計劃分析,無法輸出執行過程統計;
- executionStats 執行模式,在查詢計劃分析後,將執行winningPlan並統計過程信息;
- allPlansExecution 全計劃執行模式,將執行所有計劃(包括rejectPlans),並返回過程統計信息;
executionStats.allPlansExecution 包含了所有計劃(除winningPlan之外)的執行過程統計信息
執行計劃詳解
執行計劃將整個過程分解為多個階段,階段(stage)以樹狀結構組織,這點與執行過程是匹配的。
stage 分為多種類型,如下:
階段 | 描述 |
---|---|
COLLSCAN | 全表掃描 |
IXSCAN | 索引掃描 |
FETCH | 根據索引去檢索指定document |
PROJECTION | 限定返回字段 |
SHARD_MERGE | 將各個分片返回數據進行merge |
SORT | 表明在內存中進行了排序 |
LIMIT | 使用limit限制返回數 |
SKIP | 使用skip進行跳過 |
IDHACK | 針對_id進行查詢 |
SHARDING_FILTER | 通過mongos對分片數據進行查詢 |
COUNT | 利用db.coll.explain().count()之類進行count運算 |
COUNTSCAN | count不使用用Index進行count |
COUNT_SCAN | count使用了Index進行count |
SUBPLA | 未使用到索引的$or查詢 |
TEXT | 使用全文索引進行查詢 |
winningPlan 樣例
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"$and" : [
{
"nodeType" : {
"$eq" : "GATEWAY"
}
},
{
"creationTime" : {
"$lt" : ISODate("2017-08-08T12:34:33.125Z")
}
},
{
"creationTime" : {
"$gte" : ISODate("2017-08-08T10:34:33.125Z")
}
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"appId" : 1
},
"indexName" : "appId",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"appId" : [
"[\"s5WrMmrJV_8RBJG17FSVoY995Kga\", \"s5WrMmrJV_8RBJG17FSVoY995Kga\"]"
]
}
}
},
字段說明
屬性 | 描述 |
---|---|
winningPlan.stage | 最優計劃stage,FETCH表示根據索引檢索文檔 |
winningPlan.filter | 最優計劃的過濾器,即查詢條件 |
winningPlan.inputStage | 最優計劃stage的child stage |
winningPlan.inputStage.stage | child stage,此處是IXSCAN,表示進行index scanning |
winningPlan.inputStage.keyPattern | 掃描的索引模式 |
winningPlan.inputStage.indexName | 選用索引名稱 |
winningPlan.inputStage.isMultiKey | 是否是Multikey,如果索引建立在array上則為true |
winningPlan.inputStage.isSparse | 是否稀疏索引 |
winningPlan.inputStage.isPartial | 是否分區索引 |
winningPlan.inputStage.direction | 此query的查詢順序,此處是forward,如果用了.sort({w:-1})將顯示backward |
winningPlan.inputStage.indexBounds | 所掃描的索引範圍 |
過程統計詳解
executionStats 樣例
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 62848,
"executionTimeMillis" : 3058,
"totalKeysExamined" : 1510833,
"totalDocsExamined" : 1510833,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"$and" : [
{
"nodeType" : {
"$eq" : "GATEWAY"
}
},
{
"creationTime" : {
"$lt" : ISODate("2017-08-08T12:34:33.125Z")
}
},
{
"creationTime" : {
"$gte" : ISODate("2017-08-08T10:34:33.125Z")
}
}
]
},
"nReturned" : 62848,
"executionTimeMillisEstimate" : 2765,
"works" : 1510834,
"advanced" : 62848,
"needTime" : 1447985,
"needYield" : 0,
"saveState" : 11807,
"restoreState" : 11807,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1510833,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1510833,
"executionTimeMillisEstimate" : 792,
"works" : 1510834,
"advanced" : 1510833,
"needTime" : 0,
"needYield" : 0,
"saveState" : 11807,
"restoreState" : 11807,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"appId" : 1
},
"indexName" : "appId",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"appId" : [
"[\"s5WrMmrJV_8RBJG17FSVoY995Kga\", \"s5WrMmrJV_8RBJG17FSVoY995Kga\"]"
]
},
"keysExamined" : 1510833,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
字段說明
屬性 | 描述 |
---|---|
executionStats.executionSuccess | 是否執行成功 |
executionStats.nReturned | 返回條目數量 |
executionStats.executionTimeMilis | 執行時間(ms) |
executionStats.totalKeysExamined | 索引檢測條目 |
executionStats.totalDocsExamined | 文檔檢測條目 |
executionStats.executionStages | 執行階段詳情,大部分字段繼承於winningPlan.inputStage |
executionStats.executionStages.stage | 執行階段,FETCH表示根據索引獲取文檔 |
executionStats.executionStages.nReturned | 階段返回條目數量 |
executionStats.executionStages.executionTimeMillisEstimate | 階段執行時間 |
executionStats.executionStages.docsExamined | 階段中文檔檢測條目 |
executionStats.executionStages.works | 階段中掃描任務數 |
executionStats.executionStages.advanced | 階段中向上提交數量 |
executionStats.executionStages.needTime | 階段中定位索引位置所需次數 |
executionStats.executionStages.needYield | 階段中獲取鎖等待時間 |
executionStats.executionStages.isEOF | 階段中是否到達流的結束位,對於limit限制符的查詢可能為0 |
executionStats.executionStages.inputStage | 執行階段的子階段,這裏是一個IXSCAN的子過程 |
參考文檔
explain 官方說明
http://www.mongoing.com/eshu_explain1
https://docs.mongodb.com/manual/reference/explain-results/#explain-output
關於explain的幾種模式
https://docs.mongodb.com/manual/reference/method/cursor.explain/
理解mongo 的查詢行為
https://www.compose.com/articles/explain-explain-understanding-mongo-query-behavior/
mongo的查詢緩存
https://docs.mongodb.com/manual/core/query-plans/#index-filters
mongodb查詢操作分析