本文將為您介紹什麼是查詢計劃,以及出現查詢重規劃(Replan)的原因和處理方法。
查詢計劃器(Query Planner)
MongoDB查詢計劃器能夠根據可用的索引給每個查詢選擇並緩衝最有效查詢計劃,查詢計劃器工作流程如下圖。
查詢計劃器會根據候選計劃時查詢執行的工作單元(works)數量評估出最有效查詢計劃,成功緩衝的查詢計劃條目後續可以用於具有相同查詢樣式的查詢。
查詢計劃緩衝中的條目包含了以下3種狀態:
缺失:不存在於查詢計劃緩衝中。
非活躍:存在於查詢計劃緩衝中,並且經過評估產生了works值,可轉換成活躍狀態。
活躍:存在於查詢計劃緩衝中,曾經勝出的計劃,可轉換為非活躍狀態。
查詢計劃緩衝是完全儲存在記憶體中的,並不會持久化,MongoDB每次重啟時緩衝都會清空。此外,刪除表和刪除索引也會清理查詢計劃緩衝。查詢計劃緩衝數量存在上限,遵循LRU演算法的緩衝替代策略,因此會不定期淘汰不常命中的緩衝條目。
特殊情況下,您可以執行如下命令管理查詢計劃:
清空指定集合的查詢計劃緩衝。
db.<collection>.getPlanCache().clear()
查看指定集合下所有查詢模式。
db.<collection>.getPlanCache().listQueryShapes()
查看指定查詢的查詢計劃。
db.<collection>.getPlanCache().getPlansByQuery({"query": {"name": "testname"}, "sort": { "name": 1 })
查詢雜湊以及查詢計劃緩衝鍵
MongoDB從4.2版本開始,為了定義查詢模式(Query Shape),新增了查詢雜湊(queryHash),並為每一種不同的查詢模式產生了對應的查詢雜湊。與查詢雜湊不同,查詢計劃緩衝鍵(planCacheKey)是查詢模式和該查詢模式當前可用索引的函數。如果添加或刪除了查詢模式的索引,查詢計劃緩衝鍵可能會更改,而查詢雜湊值不會更改。
例如,一個表存在如下索引和查詢模式:
索引
db.foo.createIndex( { x: 1 } ) db.foo.createIndex( { x: 1, y: 1 } ) db.foo.createIndex( { x: 1, z: 1 }, { partialFilterExpression: { x: { $gt: 10 } } } )
查詢模式
db.foo.explain().find( { x: { $gt: 5 } } ) // 查詢操作1 db.foo.explain().find( { x: { $gt: 20 } } ) // 查詢操作2
第三個索引僅能支援查詢操作2而無法支援查詢操作1,因此這兩個查詢操作具有完全不同的查詢計劃緩衝鍵。當新增加一個{x:1, a:1}
的索引時,這兩個查詢計劃緩衝鍵也會更新。
查詢重規劃(replan)
當集合中的資料發生變化時,已緩衝的計劃可能不再是最佳選擇。由於資料是動態變化的,因此查詢計劃也要隨之變化。
當您運行與緩衝計划具有相同查詢模式的查詢時,查詢計劃器不會計算該查詢計劃,查詢計劃器將直接使用緩衝裡的計劃來執行查詢。與此同時,查詢計劃器也將持續評估該計劃的執行效率,如果查詢計劃器確定現在使用緩衝計劃的效率比另一個查詢計劃低10倍以上,那麼將停止執行,從緩衝中逐出計劃,並重新開始評估查詢計劃。上述過程就是查詢重規劃。
replan的影響以及解決方案
您可能會在慢日誌中看到關鍵字"replanned":true
,這表示資料庫無法針對特定查詢模式的查詢條件提供始終有效計劃。
慢日誌樣本如下。
"replanned":true,"replanReason":"cached plan was less efficient than expected: expected trial execution to take X works but it took at least 10X works"
影響
頻繁地發生replan可能會影響查詢效能。
大量的Query Replanning可能出現爭搶互斥鎖,導致CPU使用率過高。
解決方案
臨時升級執行個體規格以緩解資料庫負載壓力,具體操作,請參見變更配置。
嘗試清理查詢計劃,再觀察查詢分析器能否選擇到更優的計劃。
在業務端對發生replan的查詢條件使用
hint()
來指定索引,樣本如下。db.<collection>.find({a:"ABC"},{b:1,_id:0}).sort({c:1}).hint({ a:1, c:1, b:1} )
說明Hint會覆蓋查詢計劃器正常選取查詢計划行為。
在服務端對發生replan的查詢條件使用索引過濾來限制使用的索引,樣本如下。
// 設定索引過濾 db.runCommand( { planCacheSetFilter: "<collection>", query: { a: "ABC" }, projection: { b: 1, _id: 0 }, sort: { c: 1 }, indexes: [ { a: 1, c: 1 , b: 1 } ] } ) // 移除之前的設定 db.runCommand( { planCacheClearFilters: "<collection>" } )
說明索引過濾器會覆蓋查詢計劃器正常選取查詢計劃的行為。
當一個查詢同時存在Hint和索引過濾器時,索引過濾器會覆蓋指定的Hint值,因此,您應當謹慎使用索引過濾器。更多介紹,請參見Index Filters。
【推薦】最佳化查詢並為它們建立有效索引,以避免查詢重新規劃。
說明Hint和索引過濾器的方式並非最佳解決方案,在大多數情況下,檢查和修改查詢語句、可用索引和文檔模式會產生更好的結果。
【推薦】如果您的MongoDB執行個體大版本為4.2或4.4,建議將執行個體核心小版本升級至最新版,可以有效減少互斥鎖的使用。您也可以選擇將執行個體升級至5.0或6.0大版本來解決上述問題。關聯的核心JIRA ticket的說明,請參見SERVER-40805。升級資料庫小版本和大版本的方法,請參見升級資料庫小版本和升級資料庫大版本。
如果上述方法都沒有產生效果,您可以提交工單聯絡支援人員協助解決。