本文將為您介紹什么是查詢計劃,以及出現查詢重規劃(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。升級數據庫小版本和大版本的方法,請參見升級數據庫小版本和升級數據庫大版本。
如果上述方法都沒有產生效果,您可以提交工單聯系技術支持協助解決。