分享方式:


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

適用於:NoSQL

本文會逐步解說針對 Azure Cosmos DB 中查詢進行疑難排解的一般建議方法。 雖然您不應將本文所述的步驟視為潛在查詢問題的完整防禦方法,但我們已在此納入最常見的效能秘訣。 您應該使用本文作為 Azure Cosmos DB for NoSQL 中查詢緩慢或成本高昂的疑難排解起點。 您也可以使用診斷記錄來識別緩慢或耗用大量輸送量的查詢。 如果您使用的是適用於 MongoDB 的 Azure Cosmos DB API,則應使用 適用於 MongoDB 的 Azure Cosmos DB API 查詢疑難排解指南

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

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

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

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

常見的 SDK 問題

閱讀本指南之前,請先考量與查詢引擎無關的常見 SDK 問題。

  • 請遵循這些適用於查詢的 SDK 效能秘訣
  • 有時候查詢可能會有空白頁面,即使未來的頁面上有結果也一樣。 這種情況的原因可能是:
    • SDK 可能會進行多個網路呼叫。
    • 查詢可能花費很長的時間來取得文件。
  • 所有查詢都有接續權杖,可讓查詢繼續進行。 請務必完全清空查詢。 深入了解處理多個結果頁面

取得查詢計量

當您將 Azure Cosmos DB 中的查詢最佳化時,第一個步驟一律是取得查詢的查詢計量。 這些計量也可透過 Azure 入口網站取得。 在資料總管中執行查詢後,您就可以在 [結果] 索引標籤旁看到查詢計量:

取得查詢計量

取得查詢計量之後,請將擷取的文件計數與查詢的輸出文件計數進行比較。 使用此比較來識別本文中要檢查的相關章節。

擷取的文件計數是查詢引擎需要載入的文件數目。 輸出文件計數是查詢結果所需的文件數目。 如果擷取的文件計數明顯高於輸出文件計數,則表示查詢中至少有一個部分無法使用索引,而且需要進行掃描。

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

查詢的 RU 費用過高

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


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


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

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

擷取的文件計數是查詢引擎需要載入的文件數目。 輸出文件計數是查詢所傳回的文件數目。 如果擷取的文件計數明顯高於輸出文件計數,則表示查詢中至少有一個部分無法使用索引,而且需要進行掃描。

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

查詢:

SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

查詢計量:

Retrieved Document Count                 :          60,951
Retrieved Document Size                  :     399,998,938 bytes
Output Document Count                    :               7
Output Document Size                     :             510 bytes
Index Utilization                        :            0.00 %
Total Query Execution Time               :        4,500.34 milliseconds
  Query Preparation Times
    Query Compilation Time               :            0.09 milliseconds
    Logical Plan Build Time              :            0.05 milliseconds
    Physical Plan Build Time             :            0.04 milliseconds
    Query Optimization Time              :            0.01 milliseconds
  Index Lookup Time                      :            0.01 milliseconds
  Document Load Time                     :        4,177.66 milliseconds
  Runtime Execution Times
    Query Engine Times                   :          322.16 milliseconds
    System Function Execution Time       :           85.74 milliseconds
    User-defined Function Execution Time :            0.00 milliseconds
  Document Write Time                    :            0.01 milliseconds
Client Side Metrics
  Retry Count                            :               0
  Request Charge                         :        4,059.95 RUs

擷取的文件計數 (60,951) 明顯高於輸出文件計數 (7),表示此查詢會促使文件進行掃描。 在此情況下,系統函數 UPPER() 不會使用索引。

在編製索引原則中包含必要的路徑

您的索引編製原則應涵蓋 WHERE 子句、ORDER BY 子句、JOIN 和大部分系統函數中包含的任何屬性。 在索引原則中指定的所需路徑應符合 JSON 文件中的屬性。

注意

Azure Cosmos DB 編製索引原則中的屬性會區分大小寫

如果您在營養資料集上執行下列簡單查詢,當 WHERE 子句中的屬性已加入索引時,您會看到明顯降低的 RU 費用:

原始

查詢:

SELECT *
FROM c
WHERE c.description = "Malabar spinach, cooked"

編製索引原則:

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/description/*"
        }
    ]
}

RU 費用:409.51 RU

已最佳化

已更新的編製索引原則:

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": []
}

RU 費用:2.98 RU

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

了解哪些系統函數會使用索引

大部分的系統函數都會使用索引。 以下列出一些使用索引的常用字串函數:

  • StartsWith
  • 包含
  • RegexMatch
  • Left
  • Substring - 但僅當第一個 num_expr 為 0 時

以下是一些常用的系統函數,其不會使用索引且在用於 WHERE 子句時必須載入每份文件:

系統函數 最佳化的概念
Upper/Lower 不使用系統函數來將資料標準化以進行比較,而是在插入時將大小寫標準化。 SELECT * FROM c WHERE UPPER(c.name) = 'BOB' 之類的查詢就會變成 SELECT * FROM c WHERE c.name = 'BOB'
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks 計算查詢執行之前的目前時間,並在 WHERE 子句中使用該字串值。
數學函數 (非彙總) 如果您需要經常在查詢中計算值,請考慮將值儲存為 JSON 文件中的屬性。

這些系統函數可以使用索引,但在具有彙總的查詢中使用時除外:

系統函數 最佳化的概念
空間系統函數 將查詢結果儲存在即時具體化檢視中

SELECT 子句中使用時,沒有效率的系統函數將不會影響查詢使用索引的方式。

改善字串系統函數執行

針對使用索引的某些系統函數,您可以將 ORDER BY 子句新增至查詢來改善查詢執行。

更具體地說,其 RU 費用隨著屬性基數增加而增加的任何系統函數,都可能從查詢中具有 ORDER BY 而受益。 這些查詢會進行索引掃描,因此將查詢結果排序,可讓查詢更有效率。

此最佳化可改善下列系統函數的執行:

  • StartsWith (其中 case-insensitive = true)
  • StringEquals (其中 case-insensitive = true)
  • 包含
  • RegexMatch
  • EndsWith

例如,考量下列搭配 CONTAINS 的 SQL 查詢。 CONTAINS 將會使用索引,但有時,即使在新增相關索引之後,您仍可能會在執行下列查詢時觀察到非常高的 RU 費用。

原始查詢:

SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")

您可以新增 ORDER BY 來改善查詢執行:

SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
ORDER BY c.town

相同的最佳化有助於搭配額外篩選條件的查詢。 在此情況下,最好也將搭配等式篩選條件的屬性新增至 ORDER BY 子句。

原始查詢:

SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")

您可以針對 (c.name, c.town) 新增 ORDER BY複合式索引來改善查詢執行:

SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
ORDER BY c.name, c.town

了解哪些彙總查詢彙使用索引

在大部分情況下,Azure Cosmos DB 中的彙總系統函數會使用索引。 不過,視彙總查詢中的篩選條件或其他子句而定,查詢引擎可能需要載入大量的文件。 查詢引擎通常會先套用相等和範圍的篩選條件。 套用這些篩選之後,查詢引擎就可以評估其他篩選條件,並在有需要時,藉由載入其餘文件來計算彙總。

例如,假設有兩個範例查詢,同時具有相等和 CONTAINS 系統函數篩選條件的查詢,通常會比僅具有 CONTAINS 系統函數篩選條件的查詢更有效率。 這是因為會先套用「相等」篩選條件,並且會先使用索引,然後才載入必要文件來取得高成本的 CONTAINS 篩選條件。

僅使用 CONTAINS 篩選條件進行查詢 - RU 費用較高:

SELECT COUNT(1)
FROM c
WHERE CONTAINS(c.description, "spinach")

使用相等篩選條件和 CONTAINS 篩選條件進行查詢 - RU 費用較低:

SELECT AVG(c._ts)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats" AND CONTAINS(c.description, "spinach")

以下是不會完全使用索引的其他彙總查詢範例:

查詢具有不使用索引的系統函數

您應該參考相關的系統函數頁面,了解其是否使用索引。

SELECT MAX(c._ts)
FROM c
WHERE CONTAINS(c.description, "spinach")

搭配使用者定義函式 (UDF) 的彙總查詢

SELECT AVG(c._ts)
FROM c
WHERE udf.MyUDF("Sausages and Luncheon Meats")

使用 GROUP BY 的查詢

如果使用 GROUP BY,查詢的 RU 費用會隨著 GROUP BY 子句中屬性基數的增加而提高。 例如,在下列查詢中,查詢的 RU 費用會隨著唯一描述數目的增加而增加。

具有 GROUP BY 子句的彙總函式,其 RU 費用將會高於彙總函式本身的 RU 費用。 在此範例中,查詢引擎必須載入符合 c.foodGroup = "Sausages and Luncheon Meats" 篩選條件的每個文件,因此預期的 RU 費用較高。

SELECT COUNT(1)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats"
GROUP BY c.description

如果您打算經常執行相同的彙總查詢,使用 Azure Cosmos DB 變更摘要來建立即時的具體化檢視,可能會比執行個別查詢更有效率。

最佳化同時具有篩選和 ORDER BY 子句的查詢

雖然具有篩選條件和 ORDER BY 子句的查詢通常會使用範圍索引,但如果可以使用複合式索引,則會更有效率。 除了修改索引編製原則以外,您還應該將複合式索引中的所有屬性新增至 ORDER BY 子句。 此查詢變更可確保其能夠使用複合式索引。 您可以藉由在營養資料集上執行查詢來觀察影響:

原始

查詢:

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c._ts ASC

編製索引原則:

{

        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[]
}

RU 費用:44.28 RU

已最佳化

已更新的查詢 (同時在 ORDER BY 子句中包含兩個屬性):

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c.foodGroup, c._ts ASC

已更新的編製索引原則:

{  
        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[],
        "compositeIndexes":[  
            [  
                {  
                    "path":"/foodGroup",
                    "order":"ascending"
        },
                {  
                    "path":"/_ts",
                    "order":"ascending"
                }
            ]
        ]
    }

RU 費用:8.86 RU

使用子查詢來最佳化 JOIN 運算式

多值的子查詢可以藉由在每個 select-many 運算式之後推送述詞 (不是在 WHERE 子句中的所有交叉聯結之後),來最佳化 JOIN 運算式。

請考量這項查詢:

SELECT Count(1) AS Count
FROM c
JOIN t IN c.tags
JOIN n IN c.nutrients
JOIN s IN c.servings
WHERE t.name = 'infant formula' AND (n.nutritionValue > 0
AND n.nutritionValue < 10) AND s.amount > 1

RU 費用:167.62 RU

在此查詢中,索引會比對標記名稱為 infant formulanutritionValue 大於 0 且 amount 大於 1 的任何文件。 此處的 JOIN 運算式會在套用任何篩選之前,針對每個相符文件執行所有標記、營養和份數項目陣列的交叉乘積。 WHERE 子句接著會在每個 <c, t, n, s> 元組上套用篩選述詞。

例如,如果相符文件在三個陣列中分別都有 10 個項目,其會擴展為 1 x 10 x 10 x 10 (也就是 1,000) 個元組。 在這裡使用子查詢有助於篩選出聯結的陣列項目,然後再與下一個運算式聯結。

此查詢相當於上述其中一項,但是使用的是子查詢:

SELECT Count(1) AS Count
FROM c
JOIN (SELECT VALUE t FROM t IN c.tags WHERE t.name = 'infant formula')
JOIN (SELECT VALUE n FROM n IN c.nutrients WHERE n.nutritionValue > 0 AND n.nutritionValue < 10)
JOIN (SELECT VALUE s FROM s IN c.servings WHERE s.amount > 1)

RU 費用:22.17 RU

假設標記陣列中只有一個項目符合篩選條件,而且營養和份數陣列都有五個項目。 JOIN 運算式會展開為 1 x 1 x 5 x 5 = 25 個項目,而不是第一個查詢中的 1,000 個項目。

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

如果擷取的文件計數大約等於輸出文件計數,則查詢引擎就不需要掃描許多不必要的文件。 對於許多查詢,例如使用 TOP 關鍵字的查詢,擷取的文件計數可能會比輸出文件計數多出 1 個。 此狀況無須擔心。

將跨分割區查詢最小化

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

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

例如,如果您建立具有分割區索引鍵 foodGroup 的容器,下列查詢將只需要檢查單一實體分割區:

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"

對分割區索引鍵使用 IN 篩選條件的查詢只會檢查相關的實體分割區,而不會將查詢「展開傳送」:

SELECT *
FROM c
WHERE c.foodGroup IN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") and c.description = "Mushroom, oyster, raw"

對分割區索引鍵使用範圍篩選的查詢,或沒有對分割區索引鍵使用任何篩選的查詢,將需要「展開傳送」並檢查每個實體分割區的索引來取得結果:

SELECT *
FROM c
WHERE c.description = "Mushroom, oyster, raw"
SELECT *
FROM c
WHERE c.foodGroup > "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"

將具有多個屬性篩選的查詢最佳化

雖然在多個屬性上具有篩選條件的查詢通常會使用範圍索引,但如果可以使用複合式索引,則會更有效率。 對於少量的資料,這項最佳化並不會有重大影響。 不過,對於大量資料而言,這可能很實用。 您最多只能對每個複合式索引最佳化一個非相等篩選條件。 如果您的查詢有多個非相等篩選條件,請挑選其中一個來使用複合式索引。 其餘條件將繼續使用範圍索引。 非相等篩選條件必須定義在複合式索引中的最後一個。 深入了解複合式索引

以下是一些可使用複合式索引最佳化的查詢範例:

SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts = 1575503264
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts > 1575503264

以下是相關的複合式索引:

{  
        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[],
        "compositeIndexes":[  
            [  
                {  
                    "path":"/foodGroup",
                    "order":"ascending"
                },
                {  
                    "path":"/_ts",
                    "order":"ascending"
                }
            ]
        ]
}

減少查詢延遲的最佳化

在許多情況下,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 會傳回 HTTP 429 錯誤。 目前任何 API for NoSQL SDK 都會在短暫等候之後自動重試此查詢。 節流的要求需要較長的時間,因此增加佈建的輸送量可以改善查詢延遲。 您可以在 Azure 入口網站的 [計量] 分頁上,觀察已節流的要求總數

增加 MaxConcurrency

平行查詢的運作方式是以平行方式查詢多個分割。 不過,對於查詢會以循序方式擷取來自個別分割集合的資料。 因此,如果將 MaxConcurrency 設定為分割數目,將最有機會達到最高效能的查詢,但前提是其他所有系統條件皆維持不變。 如果您不知道分割區數目,可以將 MaxConcurrency (或較舊 SDK 版本中的 MaxDegreesOfParallelism) 設定為較高的數字。 系統會選擇最小值 (分割區數目、使用者提供的輸入) 作為平行處理原則的最大程度。

增加 MaxBufferedItemCount

查詢已設計成可以在用戶端處理目前的結果批次時預先擷取結果。 預先擷取有助於改善查詢的整體延遲。 設定 MaxBufferedItemCount 可限制預先擷取的結果數目。 如果您將此值設定為預期傳回的結果數目 (或較大的數目),查詢就可以從預先擷取中獲得最大的好處。 如果您將此值設定為 -1,系統就會自動判斷要緩衝的項目數目。

下一步

如需如何測量每個查詢的 RU 和取得執行統計資料來微調查詢等等的詳細資訊,請參閱下列文章: