共用方式為


針對使用 Azure Cosmos DB for MongoDB 時發生的查詢問題進行疑難排解

適用於: MongoDB

本文會逐步解說針對 Azure Cosmos DB 中查詢進行疑難排解的一般建議方法。 雖然您不應將本文所述的步驟視為潛在查詢問題的完整防禦方法,但我們已在此納入最常見的效能秘訣。 若適用於 MongoDB 的 Azure Cosmos DB API 查詢效能低落或成本高昂,應使用本文作為疑難排解的起點。 如果您使用的是 Azure Cosmos DB for NoSQL,請參閱 API for NoSQL 查詢疑難排解指南一文。

Azure Cosmos DB 中的查詢最佳化可大致分類如下:

  • 減少查詢要求單位 (RU) 費用的最佳化
  • 只降低延遲的最佳化

若降低查詢的 RU 費用,通常也會減少延遲現象。

本文提供使用營養資料集來重新建立的範例。

注意

本文假設您使用版本 3.6 及更新版本的 Azure Cosmos DB API (適用於 MongoDB) 帳戶。 在版本 3.2 中執行效能不佳的某些查詢,在版本 3.6 以上已有顯著改善。 提出升級至版本 3.6 的支援要求

使用 $explain 命令取得計量

在 Azure Cosmos DB 中最佳化查詢時,第一個步驟一律是取得查詢的 RU 費用。 大方向應是針對費用大於 50 RU 的查詢,探索能降低 RU 費用的方式。

除了取得 RU 費用以外,您也應使用 $explain 命令來取得查詢和索引使用計量。 下列範例示範執行查詢,並使用 $explain 命令來顯示查詢和索引使用計量:

$explain 命令:

db.coll.find({foodGroup: "Baby Foods"}).explain({"executionStatistics": true })

輸出:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 905.2888,
            "timeExclusiveMS" : 905.2888,
            "in" : 362,
            "out" : 362,
            "details" : {
                "database" : "db-test",
                "collection" : "collection-test",
                "query" : {
                    "foodGroup" : {
                        "$eq" : "Baby Foods"
                    }
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "e68e6bdd-5e89-4ec5-b053-3dbbc2428140",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 788.5867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 362,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 362,
                    "outputDocumentSizeBytes" : 2553535,
                    "indexHitRatio" : 0.0016802042237178,
                    "totalQueryExecutionTimeMS" : 777.72,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.19,
                        "logicalPlanBuildTimeMS" : 0.14,
                        "physicalPlanBuildTimeMS" : 0.09,
                        "queryOptimizationTimeMS" : 0.03
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 687.22,
                    "vmExecutionTimeMS" : 774.09,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 37.45,
                        "systemFunctionExecutionTimeMS" : 10.82,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 49.42
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

$explain 命令輸出較長,且有查詢執行的相關詳細資訊。 但一般而言,最佳化查詢效能時應著重於幾個部分:

計量 描述
timeInclusiveMS 後端查詢延遲
pathsIndexed 顯示查詢使用的索引
pathsNotIndexed 顯示查詢可能已使用的索引 (如有)
shardInformation 特定實體分割區的查詢效能摘要
retrievedDocumentCount 查詢引擎載入的文件數目
outputDocumentCount 查詢結果中傳回的文件數目
estimatedDelayFromRateLimitingInMilliseconds 速率限制導致的預估額外查詢延遲

取得查詢計量後,請比較查詢的 retrievedDocumentCountoutputDocumentCount。 使用此比較來識別本文中要檢查的相關章節。 retrievedDocumentCount 為查詢引擎必須載入的文件數目。 outputDocumentCount 為查詢結果所需的文件數目。 若 retrievedDocumentCount 明顯高於 outputDocumentCount,則表示查詢中至少有一個部分無法使用索引,且必須進行掃描。

請參閱下列各節,以了解您案例適用的相關查詢最佳化。

查詢的 RU 費用過高

擷取的文件計數明顯高於輸出文件計數

擷取的文件計數大約等於輸出文件計數

查詢的 RU 費用是可接受的,但延遲仍然太高

擷取文件計數超過輸出文件計數的查詢

retrievedDocumentCount 為查詢引擎必須載入的文件數目。 outputDocumentCount 是查詢所傳回的文件數目。 若 retrievedDocumentCount 明顯高於 outputDocumentCount,則表示查詢中至少有一個部分無法使用索引,且必須進行掃描。

以下是未完全由索引處理的掃描查詢範例:

$explain 命令:

db.coll.find(
  {
    $and : [
            { "foodGroup" : "Cereal Grains and Pasta"}, 
            { "description" : "Oat bran, cooked"}
        ]
  }
).explain({"executionStatistics": true })

輸出:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 436.5716,
            "timeExclusiveMS" : 436.5716,
            "in" : 1,
            "out" : 1,
            "details" : {
                "database" : "db-test",
                "collection" : "indexing-test",
                "query" : {
                    "$and" : [ 
                        {
                            "foodGroup" : {
                                "$eq" : "Cereal Grains and Pasta"
                            }
                        }, 
                        {
                            "description" : {
                                "$eq" : "Oat bran, cooked"
                            }
                        }
                    ]
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "13a5977e-a10a-4329-b68e-87e4f0081cac",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 435.4867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 1,
                    "outputDocumentSizeBytes" : 6064,
                    "indexHitRatio" : 0.0,
                    "totalQueryExecutionTimeMS" : 433.64,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.12,
                        "logicalPlanBuildTimeMS" : 0.09,
                        "physicalPlanBuildTimeMS" : 0.1,
                        "queryOptimizationTimeMS" : 0.02
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 387.44,
                    "vmExecutionTimeMS" : 432.93,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 45.36,
                        "systemFunctionExecutionTimeMS" : 16.86,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 0.13
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

retrievedDocumentCount (8618) 明顯高於 outputDocumentCount (1),表示此查詢需要文件掃描。

包含必要的索引

您應檢查 pathsNotIndexed 陣列,並新增這些索引。 在此範例中,路徑 foodGroupdescription 應編製索引。

"pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ]

在適用於 MongoDB 的 Azure Cosmos DB API 中,索引編製的最佳做法與 MongoDB 不同。 在適用於 MongoDB 的 Azure Cosmos DB API 中,複合索引僅可用於必須依多個屬性有效排序的查詢。 若您的查詢有多個屬性的篩選條件,則應為各屬性建立單一欄位索引。 查詢述詞可使用多個單一欄位索引。

萬用字元索引可簡化索引編製。 與 MongoDB 不同的是,萬用字元索引可支援查詢述詞使用多個欄位。 若您使用單一個萬用字元索引,而不是為各屬性建立個別索引,查詢效能不會有差異。 若要最佳化所有查詢,最簡單的方式是為所有屬性新增萬用字元索引。

您可隨時新增索引,而不會影響寫入或讀取可用性。 您可以追蹤索引轉換進度

瞭解使用索引的彙總作業

在適用於 MongoDB 的 Azure Cosmos DB API 中,彙總作業大多會部分使用索引。 查詢引擎通常會先套用相等和範圍篩選條件,再使用索引。 套用這些篩選之後,查詢引擎就可以評估其他篩選條件,並在有需要時,藉由載入其餘文件來計算彙總。

以下是範例:

db.coll.aggregate( [
   { $match: { foodGroup: 'Fruits and Fruit Juices' } },
   {
     $group: {
        _id: "$foodGroup",
        total: { $max: "$version" }
     }
   }
] )

在此情況下,索引可最佳化 $match 階段。 新增 foodGroup 的索引可大幅提升查詢效能。 如同在 MongoDB 中,您應及早在彙總管線中放置 $match,以充分利用索引。

在適用於 MongoDB 的 Azure Cosmos DB API 中,索引不會用於實際彙總,在此案例中為 $max。 在 version 新增索引不會改善查詢效能。

擷取文件計數等於輸出文件計數時的查詢

retrievedDocumentCount 約等於 outputDocumentCount,查詢引擎則無須掃描許多非必要文件。

將跨分割區查詢最小化

在要求單位和資料儲存體需求增加時,Azure Cosmos DB 會使用資料分割來調整個別容器。 每個實體分割區都有個別且獨立的索引。 如果您的查詢具有符合容器分割區索引鍵的相等篩選條件,您就只需要檢查相關分割區的索引。 此最佳化會減少查詢所需的 RU 總數。 深入瞭解分割區內的查詢與跨分割區查詢的差異

如果您已佈建大量的 RU (超過 30,000) 或儲存大量的資料 (大約超過 100 GB),表示您可能有夠大的容器,可看到查詢 RU 費用大幅縮減。

您可查看 shardInformation 陣列,瞭解每個個別實體分割區的查詢計量。 唯一的 shardKeyRangeId 值數目,即為必須執行查詢的實體分割區數目。 此範例中的查詢在四個實體分割區上執行。 請務必瞭解,這項執行與索引使用完全無關。 換句話說,跨分割區的查詢仍可使用索引。

  "shardInformation" : [ 
                    {
                        "activityId" : "42f670a8-a201-4c58-8023-363ac18d9e18",
                        "shardKeyRangeId" : "5",
                        "durationMS" : 24.3859,
                        "preemptions" : 1,
                        "outputDocumentCount" : 463,
                        "retrievedDocumentCount" : 463
                    }, 
                    {
                        "activityId" : "a8bf762a-37b9-4c07-8ed4-ae49961373c0",
                        "shardKeyRangeId" : "2",
                        "durationMS" : 35.8328,
                        "preemptions" : 1,
                        "outputDocumentCount" : 905,
                        "retrievedDocumentCount" : 905
                    }, 
                    {
                        "activityId" : "3754e36b-4258-49a6-8d4d-010555628395",
                        "shardKeyRangeId" : "1",
                        "durationMS" : 67.3969,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1479,
                        "retrievedDocumentCount" : 1479
                    }, 
                    {
                        "activityId" : "a69a44ee-db97-4fe9-b489-3791f3d52878",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 185.1523,
                        "preemptions" : 1,
                        "outputDocumentCount" : 867,
                        "retrievedDocumentCount" : 867
                    }
                ]

減少查詢延遲的最佳化

在許多情況下,RU 費用是可接受的,但查詢延遲仍然太高。 下列各節概述減少查詢延遲的秘訣。 若多次於相同資料集執行相同查詢,每次的 RU 費用通常會相同。 但是查詢的延遲可能會因查詢執行不同而有所差異。

改善鄰近性

如果從與 Azure Cosmos DB 帳戶不同的區域執行查詢,其延遲會高於在相同區域內執行的查詢。 例如,相較於從 Azure Cosmos DB 所在 Azure 區域內的虛擬機器執行查詢,您在桌上型電腦上執行程式碼時,應該會多出數十或數百毫秒 (或更多) 的延遲。 在 Azure Cosmos DB 中全域散發資料,以確保您可以將資料帶到應用程式附近,這是很簡單的工作。

增加佈建的輸送量

在 Azure Cosmos DB 中,您佈建的輸送量會以要求單位 (RU) 來測量。 假設您有一個會耗用 5 RU 輸送量的查詢。 例如,如果您佈建 1,000 RU,您就能夠每秒執行該查詢 200 次。 若在可用輸送量不足時嘗試執行查詢,Azure Cosmos DB 便會限制要求的速率。 等待一小段時間後,適用於 MongoDB 的 Azure Cosmos DB API 便會自動重試此查詢。 節流的要求需要較長的時間,因此增加佈建的輸送量可以改善查詢延遲。

若您增加輸送量,此值 estimatedDelayFromRateLimitingInMilliseconds 可讓您瞭解延遲的可能效益。

下一步