針對使用 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 編製索引原則中的屬性會區分大小寫
原始
查詢:
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
屬性基數增加,查詢的 GROUP BY
RU 費用會增加。 例如,在下列查詢中,查詢的 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 formula
為 、 nutritionValue
大於0且 amount
大於1之標籤的任何檔。 此處的 JOIN
表達式會在套用任何篩選之前,針對每個相符檔執行標記、營養物質及服務數位的交叉乘積。 WHERE
子句接著會在每個 <c, t, n, s>
元組上套用篩選述詞。
例如,如果相符的檔在三個陣列中各有10個專案,則會展開為1 x 10 x 10 x 10 x 10 (也就是1,000) Tuple。 在這裡使用子查詢有助於篩選出聯結的陣列項目,然後再與下一個運算式聯結。
此查詢相當於上述其中一項,但是使用的是子查詢:
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 和取得執行統計資料來微調查詢等等的詳細資訊,請參閱下列文章: