2021 iThome 鐵人賽 - DAY15 MongoDB Explain 效能分析工具

2021 iThome 鐵人賽 MongoDB披荊斬棘之路


DAY15 MongoDB Explain 效能分析工具

針對資料庫內的資料進行查詢、新增、刪修都需要迅速地找到該筆資料,因此建立索引很重要。至於要如何評估指令的效能如何,例如參數設計、順序,就需要使用 MongoDB 的 explain 指令,其他資料庫如 Oracle, MSSQL 叫做 execution plan

而 MongoDB 使用語法很簡單

1
db.collection.find().explain()

MongoDB explain 種類

MongoDB 的 explain 總共有三種模式,分別是:

  • queryPlanner
  • executionStats
  • allPlansExecution

queryPlanner

此模式下,查詢語法會透過內建的 query optimizer 選出最佳的查詢計畫,並且 評估 查詢結果,同時也會列出那些較差的查詢計畫。

無論是查詢或者增刪修,都不會實際修改資料庫的值。

若沒有設定 verbose,此模式為預設模式。

executionStats

此模式下,會根據上述的最佳計畫執行,無論是查詢或增刪修,都會去執行並且取得結果,但是不會真的去改變資料庫的值,這樣做目的當然是告訴你執行的效率如何。

allPlansExecution

基本上就是包含上述兩者。


其實 explain 功能以及呈現內容一直都隨著改版增加,所以你看到的輸出結果不見得會跟網路上其他文章一樣,不過觀念上都是一樣的,記得這點即可。


語法上有兩種方式,像是上面介紹的一種。
在參數方別帶入想要執行的模式 (queryPlanner, executionStats, allPlansExecution)

1
2
3
4
db.collection.find().explain()
db.collection.find().explain("queryPlanner")
db.collection.find().explain("executionStats")
db.collection.find().explain("allPlansExecution")

或者啟用 verbosity,預設模式就會改為 “allPlansExecution” (但還是可以修改),總之就是看哪個順手了。

1
2
3
4
5
6
7
8
9
10
11
12
db.runCommand(
{
explain: { querySyntax },
})

or

db.runCommand(
{
explain: { count: "employee", query: { age: { $gte: 30 } } },
verbosity: "executionStats"
})

MongoDB explain - queryPlanner

我們先來看看之前文章的範例資料庫中,沒有建立任何 index 情況下,不帶條件查詢的結果如何。

db.employee.find().explain()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "hellomongodb.employee", // 查詢的 db 與 collection
"indexFilterSet" : false, // 後面再講..
"parsedQuery" : {}, // 查詢條件
"winningPlan" : { // 勝出的查詢計畫
"stage" : "COLLSCAN", // 關鍵參數,查詢的使用方式
"direction" : "forward"
},
"rejectedPlans" : []
},
"serverInfo" : {
"host" : "ApieMacbook.local",
"port" : 27666,
"version" : "5.0.3",
"gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
},
"ok" : 1.0
}

serverInfo 屬於一些固定資訊,之後就不再貼上來佔版面。

暫時不解釋差異,我們先替 employee collection 建立以 name 欄位的 index

db.employee.createIndex( { name: 1 } )

再準備第二個查詢語法以及執行計畫,查詢 name 為 Devil

db.employee.find({"name":"Devil"}).explain()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "hellomongodb.employee",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "Devil"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"Devil\", \"Devil\"]"
]
}
}
},
"rejectedPlans" : []
},
  • parsedQuery 就是你的查詢條件
  • winningPlan 系統選出的查詢,在剛開始不帶任何條件時,結果是這樣
1
2
3
4
"winningPlan" : {
"stage" : "COLLSCAN",
"direction" : "forward"
},

而使用了索引去查詢,會有這樣結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"Devil\", \"Devil\"]"
]
}}}

stage 從 COLLSCAN 變成 IXSCAN。
Stage 有以下幾種:

  • COLLSCAN 掃描整個 Collection
  • IXSCAN 根據 Index 進行掃描
  • FETCH 根據 Index 進行掃描資料(Document)
  • SHARD_MERGE 合併各分片(shards)取得的資料
  • SHARDING_FILTER for filtering out orphan documents from shards (這邊我直接使用官方的說明,之後再補上情境)

基本上看到 COLLSCAN 就是完全禁止的,代表你的查詢參數、語法沒辦法有效率的取得資料,原本幾毫秒的查詢可能會變成秒級以上。

所以定期的檢驗所有查詢,看是否有 COLLSCAN 是非常重要的。MongoDB 內建的 profiler 也有相關功能,有興趣也可以找一下之前寫的文章。MongoDB Atlas 也有功能,但沒直接指出來是哪個查詢,有點可惜。


本篇講解了如何使用 explain 指令去分析你的查詢語法,基本上已經非常足夠使用在大部分的索引設計和使用情境,一定要確保在開需求時,使用情境有符合資料庫的設計,否則實作下去了,會受限於技術設計而影響到使用者情境。

下一篇文章再開始講 executionStats 內的項目。

  • 作者: MingYi Chou
  • 版權聲明: 轉載不用問,但請註明出處!本網誌均採用 BY-NC-SA 許可協議。