本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
查詢規劃器 v2
Amazon DocumentDB (規劃器 2.0 版) 的新查詢規劃器具有進階查詢最佳化功能並改善效能。使用 find和 update運算子搭配索引時,適用於 Amazon DocumentDB 5.0 的規劃器 2.0 版可提供比先前版本高達 10 倍的效能改善。效能改善主要來自使用更理想的索引計劃,以及為否定運算子 ($neq、$nin) 和巢狀 等運算子啟用索引掃描支援$elementMatch。規劃器 2.0 版查詢透過更好的成本估算技術、最佳化演算法和增強的穩定性,更快速地執行。規劃器 2.0 版也支援計畫快取篩選條件 APIs,可增強規劃器穩定性。使用此功能,Amazon DocumentDB 5.0 現在提供從不同版本的查詢規劃器中選擇的功能。
先決條件
下列先決條件適用於規劃器 2.0 版:
引擎 5.0 版的所有 AWS 區域 都提供規劃器 2.0 版。
若要選擇使用 2.0 版做為預設查詢規劃器,您的叢集必須位於 Amazon DocumentDB 5.0 版的引擎修補程式 3.0.15902 版或更新版本。如需更新至最新引擎版本修補程式的步驟,請參閱 執行叢集引擎版本的修補程式更新。
若要將規劃器 2.0 版設定為預設查詢規劃器,您需要 IAM 許可才能更新叢集參數群組。
對於 Amazon DocumentDB 8.0,規劃器 3.0 版是預設查詢規劃器。如需詳細資訊,請參閱 查詢規劃器 v3。
選取規劃器 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 版。
有些查詢形狀無法設定計劃快取篩選條件。如需目前的清單,請參閱 支援的查詢形狀。
Find 和 Update運算子的改進
規劃器 2.0 版最佳化基本操作update,包括 find、delete、 和 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]})
計劃快取篩選條件
規劃器 2.0 版新增了對計劃快取篩選條件的支援,也稱為索引篩選條件,可讓您限制規劃器針對特定查詢形狀考慮的索引集。篩選條件是使用資料庫命令設定並套用到伺服器上,因此如果您遇到查詢迴歸,則可以減輕它,而無需修改應用程式程式碼。
在規劃器 2.0 版上,計劃快取篩選條件適用於 find和 count命令,從 Amazon DocumentDB 5.0.2 和 8.0.2 開始也適用於 update、 delete和 findAndModify命令。distinct 和 aggregate命令需要規劃器 3.0 版。
如需有關命令參考、支援的查詢形狀、工作範例,以及篩選條件如何與提示互動的詳細資訊,請參閱 計劃快取篩選條件。
規劃器版本 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})
當$(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 }