查詢規劃器 - Amazon DocumentDB

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

查詢規劃器

Amazon DocumentDB (規劃器 2.0 版) 的新查詢規劃器具有進階查詢最佳化功能並改善效能。使用 findupdate運算子搭配索引時,適用於 Amazon DocumentDB 5.0 的規劃器 2.0 版可提供比先前版本高達 10 倍的效能改善。效能改善主要來自使用更理想的索引計畫,以及為否定運算子 ($neq$nin) 和巢狀 等運算子啟用索引掃描支援$elementMatch。規劃器 2.0 版查詢透過更好的成本估算技術、最佳化演算法和增強的穩定性,更快速地執行。規劃器 2.0 版也支援計畫快取篩選條件 APIs,可增強規劃器穩定性。使用此功能,Amazon DocumentDB 5.0 現在提供從不同版本的查詢規劃器中選擇的功能。

先決條件

下列先決條件適用於規劃器 2.0 版:

  • 規劃器 2.0 版可在引擎 5.0 版提供的所有區域使用。

  • 若要選擇使用 2.0 版做為預設查詢規劃器,您的叢集必須位於 Amazon DocumentDB 5.0 版的引擎修補程式 3.0.15902 版或更新版本。如需更新至最新引擎版本修補程式的步驟,請參閱 對叢集的引擎版本執行修補程式更新

  • 若要將規劃器 2.0 版設定為預設查詢規劃器,您需要 IAM 許可才能更新叢集參數群組。

選取規劃器 2.0 版做為預設查詢規劃器

使用下列步驟,從主控台或 CLI 選取 2.0 作為預設查詢規劃器:

  • 請依照中的步驟修改 Amazon DocumentDB 叢集參數修改叢集的參數群組。

  • 對於名為「plannerVersion」的參數,將值變更為 2.0,表示規劃器版本 2.0。

  • 選取立即套用 (選取在重新開機時套用會使選取無效,直到叢集下次重新開機為止)。

最佳實務

如需預期結果,請在套用規劃器 2.0 版時使用下列最佳實務:

  • 在全域叢集中,選取兩個區域的叢集參數群組中的相同plannerVersion值 (1.0 或 2.0)。請注意,在主要和次要區域中選取不同的規劃器版本可能會導致查詢行為和效能不一致。

  • 在排程維護時段或減少流量期間更新至規劃器 2.0 版將最不造成干擾,因為如果在工作負載主動執行時變更規劃器版本,可能會提高錯誤率。

  • 規劃器 2.0 版最適用於 MongoDB shell 5.0 版。

限制

下列限制適用於規劃器 2.0 版:

  • 彈性叢集不支援規劃器 2.0 版,這會回到規劃器 1.0 版。

  • 彙總和不同的命令不支援規劃器 2.0 版,這會回到規劃器 1.0 版。

  • 規劃器 2.0 版中的計劃快取篩選條件不支援包含 regex、文字搜尋、地理空間、jsonschema 或篩選條件$expr的查詢。

FindUpdate運算子的改進

規劃器 2.0 版最佳化基本操作update,包括 finddelete、 和 find-and-modify命令。下列標籤區段顯示索引的增強功能,以及規劃器 2.0 版的查詢效能改善:

Enhanced index support
  • 規劃器 2.0 版新增了對否定運算子的索引支援$ne,包括 $nin$not {eq}、 和 $not {in},以及 $type$elemMatch

    Sample Document: { "x": 10, "y": [1, 2, 3] } db.foo.createIndex({ "x": 1, "y": 1 }) db.foo.find({ "x": {$nin: [20, 30] }}) db.foo.find({"x":{ $type: "string" }}) db.foo.createIndex({"x.y": 1}) db.foo.find({"x":{$elemMatch:{"y":{$elemMatch:{"$gt": 3 }}}}})
  • 即使查詢表達式中$exists不存在 ,規劃器 2.0 版仍會利用稀疏或部分索引。

    Sample Document: {"name": "Bob", "email": "example@fake.com" } Using Planner Version 1.0, you can specify the command as shown below: db.foo.find({email: "example@fake.com", email: {$exists: true}}) Using Planner Version 2.0, you can specify command without $exists: db.foo.find({ email: "example@fake.com" })
  • 即使查詢條件未完全符合部分索引篩選條件表達式,規劃器 2.0 版仍會利用部分索引。

    Sample Document: {"name": "Bob", "age": 34} db.foo.createIndex({"age":1},{partialFilterExpression:{"age":{$lt:50}}}) With Planner Version 1.0, index is used only when the query condition meets the partial index filter criterion: db.foo.find({"age":{$lt:50}}) With Planner Version 2.0, index is used even when the query condition doesn’t meet the index criterion: db.foo.find({"age":{$lt:30}})
  • 規劃器 2.0 版使用部分索引掃描搭配 $elemMatch 查詢。

    Sample Document: {"name": "Bob", "age": [34,35,36]} db.foo.createIndex({"age":1},{partialFilterExpression:{"age":{$lt:50,$gt:20}}}) db.foo.find({age:{$elemMatch:{$lt:50,$gt:20}}})
  • 規劃器 2.0 版包含 的索引掃描支援$regex,而不需要在應用程式程式碼$hint中提供 。 僅$regex支援字首搜尋的索引。

    Sample Document: { "x": [1, 2, 3], "y": "apple" } db.foo.createIndex({ "x": 1, "y": 1 }) db.foo.find({"y":{ $regex: "^a" }})
  • 規劃器 2.0 版可改善涉及多金鑰索引的查詢效能,以及多金鑰欄位的相等條件。

    Sample Document: {"x": [1, 2, 3], "y": 5} db.foo.createIndex({"x": 1, "y":1}) db.foo.find({"x": 2, "y": {$gt: 1}}).limit(1)
  • 規劃器 2.0 版可改善涉及多個篩選條件的查詢效能,尤其是文件大於 8 KB 的集合。

    Sample Document: {"x": 2, "y": 4, "z": 9, "t": 99} db.foo.find({$and: [{"x": {$gt : 1}, "y": {$gt : 3}, "z": {$lt : 10}, "t":{$lt : 100}}]})
  • 規劃器 2.0 版透過消除排序階段,在將$in運算子與複合索引搭配使用時改善查詢的效能。

    Sample Document: {"x": 2, "y": 4, "z": 9, "t": 99} db.foo.createIndex({"x":1, "y":1}) db.foo.find({"x":2, "y":$in:[1,2,3,4]}).sort({x:1,y:1})

    它也可以改善使用多索引鍵索引搭配 $in元素的查詢效能。

    Sample Document: {"x": [1, 2, 3]} db.foo.createIndex({"x": 1}) db.foo.find("x":{$in:[>100 elements]})
Query performance improvements
  • 規劃器 2.0 版可改善涉及多個篩選條件的查詢效能,尤其是文件大於 8 KB 的集合。

    Sample Document: {"x": 2, "y": 4, "z": 9, "t": 99} db.foo.find({$and: [{"x": {$gt : 1}, "y": {$gt : 3}, "z": {$lt : 10}, "t":{$lt : 100}}]})
  • 規劃器 2.0 版透過消除排序階段,在將$in運算子與複合索引搭配使用時改善查詢的效能。

    Sample Document: {"x": 2, "y": 4, "z": 9, "t": 99} db.foo.createIndex({"x":1, "y":1}) db.foo.find({"x":2, "y":$in:[1,2,3,4]}).sort({x:1,y:1})

    它也可以改善使用多索引鍵索引搭配 $in元素的查詢效能。

    Sample Document: {"x": [1, 2, 3]} db.foo.createIndex({"x": 1}) db.foo.find("x":{$in:[>100 elements]})

計劃快取篩選條件 API

注意

計劃快取篩選條件不支援文字索引。

  • 規劃器 2.0 版新增索引篩選功能的支援,可讓您指定特定查詢形狀可以使用的索引清單。此功能可透過 API 存取,並且可以從伺服器端控制。如果您遇到查詢迴歸,此功能可讓您更快速、更靈活地解決問題,而無需修改應用程式程式碼。

    db.runCommand({ planCacheSetFilter: <collection>, query: <query>, sort: <sort>, // optional, indexes: [ <index1>, <index2>, ...], comment: <any> // optional})

    若要列出集合上的所有篩選條件,請使用下列命令:

    db.runCommand( { planCacheListFilters: <collection> } )

    此命令會顯示集合上的所有索引篩選條件。輸出範例:

    { "filters" : [ { "query" : {a: "@", b: "@"}, "sort" : {a: 1}, "indexes" : [ <index1>, ... ] }, ... ], "ok": 1 }
  • 您可以從explain命令輸出使用兩個新欄位來分析規劃器 2.0 版的索引篩選: indexFilterSetindexFilterApplied。如果集合上設定了符合查詢形狀的索引篩選條件,indexFilterSet則 設定為「true」。如果 ,且只有在查詢套用索引篩選條件並使用篩選條件清單中的索引選擇計劃時, indexFilterApplied 才會設定為「true」。

    您可以使用下列命令清除索引篩選條件:

    db.runCommand( { planCacheClearFilters: <collection>> query: <query pattern>, // optional sort: <sort specification>, // optional comment: <any>. //optional } )

    若要清除集合 "foo" 上的所有篩選條件,請使用下列命令:

    db.runCommand({planCacheClearFilters: "foo"})

    若要清除具有任何排序的特定查詢形狀,您可以從 的輸出複製並貼上查詢形狀planCacheListFilters

    db.runCommand({planCacheClearFilters: "foo", query: {a: @}})

    若要清除具有要排序之指定欄位的特定查詢形狀,您可以從 的輸出複製並貼上查詢形狀planCacheListFilters

    db.runCommand({planCacheClearFilters: "foo", query: {a: @},sort: {a: 1}})

規劃器版本 1.0、2.0 和 MongoDB 之間的潛在行為差異

在某些情況下,規劃器 2.0 版可能會產生與 MongoDB 中結果稍有不同的結果。本節會逐步解說這些可能性的一些範例。

$(update) and $(projection)
  • 在某些情況下,MongoDB 中的 $(update)$(projection)運算子的行為可能與 Amazon DocumentDB 的規劃器 1.0 版不同。以下是一些範例:

    db.students_list.insertMany( [ { _id: 5, student_ids: [ 100, 200 ], grades: [ 95, 100 ], grad_year: [ 2024, 2023 ] } ] )
    db.students_list.updateOne({ student_ids: 100, grades: 100, grad_year: 2024 }, { $set: { “grad_year.$”: 2025 } }
    • 規劃器 1.0 版 — 更新欄位 2022

    • MongoDB — 更新欄位 2022

    • 規劃器 2.0 版 — 更新欄位 2021

  • db.col.insert({x:[1,2,3]}) db.col.update({$and:[{x:1},{x:3}]},{$set:{"x.$":500}})
    • 規劃器 1.0 版 — 隨機更新第一個相符的元素

    • MongoDB — 隨機更新第一個相符的元素

    • 規劃器 2.0 版 — 不進行更新

  • db.col.insert({x:[1,2,3]}) db.col.find()
    • 規劃器 1.0 版 — 隨機選取相符的元素

    • MongoDB — 隨機選取相符的元素

    • 規劃器 2.0 版 — 不進行選擇

  • db.col.insert({x:100}) db.col.update({x:100},{x:100})
    • 規劃器 1.0 版 — nModified變更

    • MongoDB — nModified count 變更

    • 規劃器 2.0 版 — 使用相同值更新時,nModified count 不會變更。

  • $(update)運算子與 搭配使用時$setOnInsert,規劃器 1.0 版和 MongoDB 會擲回錯誤,但規劃器 2.0 版不會。

  • 重新命名不存在的欄位以在規劃器 2.0 版中$field擲回錯誤,而 不會在規劃器 1.0 版和 MongoDB 中產生更新。

Index behavior
  • 當 套用不適合的索引$hint時,規劃器 2.0 版會擲回錯誤,而規劃器 1.0 版和 MongoDB 則不會。

    // Insert db.col.insert({x:1}) db.col.insert({x:2}) db.col.insert({x:3}) // Create index on x with partialFilter Expression {x:{$gt:2}} db.col.createIndex({x:1},{partialFilterExpression:{x:{$gt:2}}}) // Mongodb allows hint on the following queries db.col.find({x:1}).hint("x_1") // result is no documents returned because {x:1} is not indexed by the partial index // Without $hint mongo should return {x:1}, thus the difference in result between COLSCAN and IXSCAN DocumentDB will error out when $hint is applied on such cases. db.col.find({x:1}).hint("x_1") Error: error: { "ok" : 0, "operationTime" : Timestamp(1746473021, 1), "code" : 2, "errmsg" : "Cannot use Hint for this Query. Index is multi key index , partial index or sparse index and query is not optimized to use this index." } rs0:PRIMARY> db.runCommand({"planCacheSetFilter": "col", "query": { location: {$nearSphere: {$geometry: {type: "Point", coordinates: [1, 1]}}}}, "indexes": ["name_1"]}) { "ok" : 0, "operationTime" : Timestamp(1750815778, 1), "code" : 303, "errmsg" : "Unsupported query shape for index filter $nearSphere" }
  • $near 無法在規劃器 2.0 版$hint({“$natural”:1})中使用 。

    // indexes present are index on x and geo index rs0:PRIMARY> db.usarestaurants.getIndexes() [ { "v" : 4, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.usarestaurants" }, { "v" : 4, "key" : { "location" : "2dsphere" }, "name" : "location_2dsphere", "ns" : "test.usarestaurants", "2dsphereIndexVersion" : 1 } ] // Planner Version 2.0 will throw an error when $hint is applied with index "x_1" rs0:PRIMARY> db.usarestaurants.find({ "location":{ "$nearSphere":{ "$geometry":{ "type":"Point", "coordinates":[ -122.3516, 47.6156 ] }, "$minDistance":1, "$maxDistance":2000 } } }, { "name":1 }).hint({"$natural": 1}) Error: error: { "ok" : 0, "operationTime" : Timestamp(1746475524, 1), "code" : 291, "errmsg" : "unable to find index for $geoNear query" } // Planner Version 1.0 and MongoDB will not throw an error db.usarestaurants.find({ "location":{ "$nearSphere":{ "$geometry":{ "type":"Point", "coordinates":[ -122.3516, 47.6156 ] }, "$minDistance":1, "$maxDistance":2000 } } }, { "name":1 }).hint({"$natural": 1}) { "_id" : ObjectId("681918e087dadfd99b7f0172"), "name" : "Noodle House" }
  • 雖然 MongoDB 支援完整的 regex 索引掃描,但規劃器 2.0 版僅支援字首欄位的 regex 索引掃描。

    // index on x db.col.createIndex({x:1}) // index scan is used only for prefix regexes rs0:PRIMARY> db.col.find({x: /^x/}).explain() { "queryPlanner" : { "plannerVersion" : 2, "namespace" : "test.col", "winningPlan" : { "stage" : "IXSCAN", "indexName" : "x_1", "direction" : "forward", "indexCond" : { "$and" : [ { "x" : { "$regex" : /^x/ } } ] }, "filter" : { "x" : { "$regex" : /^x/ } } } }, "indexFilterSet" : false, "indexFilterApplied" : false, "ok" : 1, "operationTime" : Timestamp(1746474527, 1) } // COLSCAN is used for non-prefix regexes rs0:PRIMARY> db.col.find({x: /x$/}).explain() { "queryPlanner" : { "plannerVersion" : 2, "namespace" : "test.col", "winningPlan" : { "stage" : "COLLSCAN", "filter" : { "x" : { "$regex" : /x$/ } } } }, "indexFilterSet" : false, "indexFilterApplied" : false, "ok" : 1, "operationTime" : Timestamp(1746474575, 1)
  • 相較於 MongoDB,搭配規劃器 2.0 版使用計劃快取篩選條件有一些固有差異。雖然規劃器 2.0 版不支援使用計劃快取篩選條件指定「投影」和「定序」,但 MongoDB 會這麼做。不過,MongoDB 索引篩選條件僅限記憶體內,重新啟動後會遺失。規劃器 2.0 版透過重新啟動和修補程式保留索引篩選條件。

Others
  • 使用規劃器 2.0 版時的 DML 稽核日誌格式與規劃器 1.0 版略有不同。

    command - db.col.find({x:1}) ************** Audit logs generated ****************** // v1 format for dml audit logs {"atype":"authCheck","ts":1746473479983,"timestamp_utc":"2025-05-05 19:31:19.983","remote_ip":"127.0.0.1:47022","users":[{"user":"serviceadmin","db":"test"}],"param":{"command":"find","ns":"test.col","args":{"batchSize":101,"filter":{"x":1},"find":"col","limit":18446744073709551615,"lsid":{"id":{"$binary":"P6RCGz9ZS4iWBSSHWXW15A==","$type":"4"},"uid":{"$binary":"6Jo8PisnEi3dte03+pJFjdCyn/5cGQL8V2KqaoWsnk8=","$type":"0"}},"maxScan":18446744073709551615,"singleBatch":false,"skip":0,"startTransaction":false},"result":0}} // v2 formal for dml audit logs {"atype":"authCheck","ts":1746473583711,"timestamp_utc":"2025-05-05 19:33:03.711","remote_ip":"127.0.0.1:37754","users":[{"user":"serviceadmin","db":"test"}],"param":{"command":"find","ns":"test.col","args":{"find":"col","filter":{"x":1},"lsid":{"id":{"$binary":"nJ88TGCSSd+BeD2+ZtrhQg==","$type":"4"}},"$db":"test"},"result":0}}
  • 做為解釋計畫一部分的索引條件:

    rs0:PRIMARY> db.col.createIndex({index1:1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1, "operationTime" : Timestamp(1761149251, 1) }

    規劃器 2.0 版說明顯示索引條件和篩選條件的計畫輸出:

    rs0:PRIMARY> db.col.find({$and:[{price:{$eq:300}},{item:{$eq:"apples"}}]}).explain() { "queryPlanner" : { "plannerVersion" : 2, "namespace" : "test.col", "winningPlan" : { "stage" : "IXSCAN", "indexName" : "price_1", "direction" : "forward", "indexCond" : { "$and" : [ { "price" : { "$eq" : 300 } } ] }, "filter" : { "$and" : [ { "item" : { "$eq" : "apples" } } ] } } }, "indexFilterSet" : false, "indexFilterApplied" : false, "ok" : 1, "operationTime" : Timestamp(1761149497, 1) }

    規劃器 1.0 版說明計畫輸出:

    rs0:PRIMARY> db.col.find({$and:[{price:{$eq:300}},{item:{$eq:"apples"}}]}).explain() { "queryPlanner" : { "plannerVersion" : 1, "namespace" : "test.col", "winningPlan" : { "stage" : "IXSCAN", "indexName" : "price_1", "direction" : "forward" } }, "ok" : 1, "operationTime" : Timestamp(1761149533, 1) }

規劃器 2.0 版使用 MongoDB 彌補行為差距

規劃器 2.0 版在部分區域會縮小 MongoDB 的行為差距:

  • 規劃器 2.0 版允許對 的平面陣列進行數值索引查詢$elemMatch

    doc: {"x" : [ [ { "y" : 1 } ] ] } // Planner Version 2 and mongo > db.bar.find({"x.0": {$elemMatch: {y: 1}}}) { "_id" : ObjectId("68192947945e5846634c455a"), "x" : [ [ { "y" : 1 } ] ] } > db.bar.find({"x": {$elemMatch: {"0.y": 1}}}) { "_id" : ObjectId("68192947945e5846634c455a"), "x" : [ [ { "y" : 1 } ] ] } //Whereas Planner Version 1 wouldn't return any results. > db.bar.find({"x.0": {$elemMatch: {y: 1}}}) > db.bar.find({"x": {$elemMatch: {"0.y": 1}}})
  • 雖然規劃器 1.0 版在投影中排除了字串,但規劃器 2.0 版的行為與 MongoDB 一致,並將其視為常值」

    // Planner V2/ MongoDB > db.col.find() { "_id" : ObjectId("681537738aa101903ed2fe05"), "x" : 1, "y" : 1 } > db.col.find({},{x:"string"}) { "_id" : ObjectId("681537738aa101903ed2fe05"), "x" : "string" } // Planner V1 treats strings as exclude in projection rs0:PRIMARY> db.col.find() { "_id" : ObjectId("68153744d42969f11d5cca72"), "x" : 1, "y" : 1 } rs0:PRIMARY> db.col.find({},{x:"string"}) { "_id" : ObjectId("68153744d42969f11d5cca72"), "y" : 1 }
  • 規劃器 2.0 版與 MongoDB 一樣,不允許對相同欄位「x」和「x.a」進行投影:

    // Planner version 2/MongoDB will error out > db.col.find() { "_id" : ObjectId("68153da2012265816bc9ba23"), "x" : [ { "a" : 1 }, 3 ] } db.col.find({},{"x.a":1,"x":1}) // error // Planner Version 1 does not error out db.col.find() { "_id" : ObjectId("68153da2012265816bc9ba23"), "x" : [ { "a" : 1 }, 3 ] } db.col.find({},{"x.a":1,"x":1}) { "_id" : ObjectId("68153d60143af947c720d099"), "x" : [ { "a" : 1 }, 3 ] }
  • 規劃器 2.0 版,例如 MongoDB,允許子文件上的投影:

    // Planner Version2/MongoDB supports projections on subdocuments db.col.find() { "_id" : ObjectId("681542d8f35ace71f0a50004"), "x" : [ { "y" : 100 } ] } > db.col.find({},{"x":{"y":1}}) { "_id" : ObjectId("681542b7a22d548e4ac9ddea"), "x" : [ { "y" : 100 } ] } // Planner V1 throws error if projection is subdocument db.col.find() { "_id" : ObjectId("681542d8f35ace71f0a50004"), "x" : [ { "y" : 100 } ] } rs0:PRIMARY> db.col.find({},{"x":{"y":1}}) Error: error: { "ok" : 0, "operationTime" : Timestamp(1746223914, 1), "code" : 2, "errmsg" : "Unknown projection operator y" }
  • 使用規劃器 2.0 版,例如 MongoDB,投影不支援運算$子後面的欄位:

    // Mongo and Planner Version 2 will error out db.col.find() { "_id" : ObjectId("68155fa812f843439b593f3f"), "x" : [ { "a" : 100 } ] } db.col.find({"x.a":100},{"x.$.a":1}) - // error // v1 will not error out db.col.find() { "_id" : ObjectId("68155fa812f843439b593f3f"), "x" : [ { "a" : 100 } ] } db.col.find({"x.a":100},{"x.$.a":1}) { "_id" : ObjectId("68155dee13b051d58239cd0a"), "x" : [ { "a" : 100 } ] }
  • 規劃器 2.0 版,例如 MongoDB,允許$hint使用:

    // v1 will error out on $hint if there are no filters db.col.find({}).hint("x_1") Error: error: { "ok" : 0, "operationTime" : Timestamp(1746466616, 1), "code" : 2, "errmsg" : "Cannot use Hint for this Query. Index is multi key index , partial index or sparse index and query is not optimized to use this index." } // Mongo and Planner Version 2 will allow $hint usage db.col.find({}).hint("x_1") { "_id" : ObjectId("6818f790d5ba9359d68169cf"), "x" : 1 }