使用 Azure Cosmos DB 調整查詢效能
適用於:NoSQL
Azure Cosmos DB 提供適用於查詢資料的 API for NoSQL,而不需結構描述或次要索引。 本文可為開發人員提供下列資訊:
- 關於 Azure Cosmos DB 之 SQL 查詢執行如何運作的高階詳細資料
- 查詢效能的秘訣和最佳作法
- 如何運用 SQL 查詢執行計量來偵錯查詢效能的範例
在 Azure Cosmos DB 中,會將資料儲存至容器,而此容器可以成為為任何儲存體大小或要求輸送量。 Azure Cosmos DB 會在涵蓋範圍內,於實體分割區之間順暢地調整資料,以處理資料成長或提高佈建的輸送量。 您可以使用 REST API 或其中一個支援的 SQL SDK,向任何容器發出 SQL 查詢。
分割的簡短概觀:您定義分割區索引鍵 (例如 "city"),以決定在實體分割區之間分割資料的方式。 屬於單一分割區索引鍵的資料 (例如,"city" == "Seattle") 會儲存至實體分割區,而且單一實體分割區可以儲存來自多個分割區索引鍵的資料。 分割區達到其儲存體限制時,服務會以無縫方式將分割區分割成兩個新的分割區。 資料會平均分散到新的分割區,並將單一分割區索引鍵的所有資料都保存在一起。 因為分割區是暫時性的,所以 API 會使用分割區索引鍵範圍的抽象概念來代表分割區索引鍵雜湊的範圍。
當您向 Azure Cosmos DB 發出查詢時,SDK 會執行下列邏輯步驟:
- 剖析 SQL 查詢以決定查詢執行計畫。
- 如果查詢所包含的篩選係以分割區索引鍵為依據 (例如
SELECT * FROM c WHERE c.city = "Seattle"
),則會將該查詢路由傳送至單一分割區。 如果查詢沒有根據分割區索引鍵的篩選,則會在所有分割區中執行該查詢,而且會在用戶端合併每個分割區的結果。 - 根據用戶端設定而定,查詢會依序或平行地在每個分割區內執行。 在每個分割區內,根據查詢複雜度、設定的頁面大小,以及集合的佈建輸送量而定,查詢可能會進行一或多次來回行程。 每次執行都會傳回查詢執行所取用的要求單位數目,以及查詢執行統計資料。
- SDK 會跨分割區執行查詢結果的摘要。 例如,如果查詢跨分割區包含 ORDER BY,則來自個別分割區的結果就會依序合併,以全域排序順序傳回結果。 如果查詢是
COUNT
之類的彙總,即會將來自個別分割區的計數加總以產生整體計數。
SDK 提供適用於查詢執行的各種選項。 例如,在 .NET 中,下列選項適用於 QueryRequestOptions
類別。 下表描述這些選項,以及其對查詢執行時間的影響。
選項 | 描述 |
---|---|
EnableScanInQuery |
只有在已停用針對所要求篩選路徑編製索引的功能時才適用。 如果您已選擇不編製索引,而且想要使用完整掃描來執行查詢,則必須設定為 true。 |
MaxItemCount |
在每次到伺服器的來回行程中要傳回的項目數上限。 您可以將其設定為 -1,以讓伺服器管理所傳回的項目數。 |
MaxBufferedItemCount |
平行查詢執行期間,可在用戶端緩衝處理的項目數上限。 正的屬性值會將所緩衝處理的項目數限制為設定的值。 您可以將其設定為小於 0,以讓系統自動決定要緩衝處理的項目數。 |
MaxConcurrency |
取得或設定平行查詢執行期間,在用戶端執行的並行作業數目。 正值的屬性值會將並行操作次數限制為設定的值。 您可以將其設定為小於 0,以讓系統自動決定要執行的並行作業數目。 |
PopulateIndexMetrics |
啟用索引計量的收集,以瞭解查詢引擎如何使用現有的索引,以及其如何使用潛在的新索引。 此選項會產生額外負荷,因此只有在偵錯讓查詢變慢時才應該啟用。 |
ResponseContinuationTokenLimitInKb |
您可以限制伺服器所傳回之接續 Token 的大小上限。 如果您的應用程式主機具有回應標頭大小限制,則您可能需要設定此項目,但其可能會增加查詢所耗用的整體持續時間和 RU。 |
例如,以下是使用 .NET SDK 以在依 /city
所分割的容器上的查詢:
QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.city = 'Seattle'");
QueryRequestOptions options = new QueryRequestOptions()
{
MaxItemCount = -1,
MaxBufferedItemCount = -1,
MaxConcurrency = -1,
PopulateIndexMetrics = true
};
FeedIterator<dynamic> feedIterator = container.GetItemQueryIterator<dynamic>(query);
FeedResponse<dynamic> feedResponse = await feedIterator.ReadNextAsync();
每個查詢執行頁面都會對應至具有針對查詢要求選項所設定標頭的 REST API POST
以及主體中的 SQL 查詢。 如需 REST API 要求標頭和選項的詳細資訊,請參閱使用 REST API 查詢資源。
下列因素通常會對 Azure Cosmos DB 查詢效能造成最大影響。 我們將在本文中更深入探討下列每個因素。
係數 | 提示 |
---|---|
佈建的輸送量 | 測量每個查詢的 RU,並確認您擁有查詢所需的佈建輸送量。 |
分割區和分割區索引鍵 | 在篩選子句中偏好使用具有分割區索引鍵的查詢以降低延遲。 |
SDK 與查詢選項 | 遵循 SDK 最佳作法 (例如直接連線),並調整用戶端查詢執行選項。 |
網路延遲 | 盡可能在與 Azure Cosmos DB 帳戶相同的區域中執行應用程式,以減少延遲。 |
編製索引原則 | 確定您具有查詢所需的編製索引路徑/原則。 |
查詢執行計量 | 分析查詢執行計量,以識別可能發生重新寫入的查詢與資料圖形。 |
在 Azure Cosmos DB 中,您會建立資料的容器,每一個容器都具有以每秒要求單位 (RU) 表示的保留輸送量。 讀取 1 KB 文件就是一個 RU,而每個作業 (包括查詢) 都會根據其複雜度正規化為固定數目的 RU。 例如,如果您已針對容器佈建 1000 RU/秒,而且具有類似 SELECT * FROM c WHERE c.city = 'Seattle'
的查詢 (其取用 5 個 RU),則您每秒可以執行這些查詢中的 (1000 RU/秒)/(5 RU/查詢) = 200 個查詢。
如果您每秒提交 200 個查詢 (或讓所有已佈建 RU 飽和的一些其他作業),則服務會啟動速率限制傳入要求。 SDK 會執行輪詢/重試來自動速率限制,因此您可能會注意到這些查詢具有較高的延遲。 將佈建的輸送量提高為要求的值,即可改善您的查詢延遲和輸送量。
若要深入了解要求單位,請參閱要求單位。
使用 Azure Cosmos DB 時,下列資料讀取情節會從通常最快/最具效率的資料排序到最慢/最不具效率的資料。
- 單一分割區索引鍵和項目索引鍵上的 GET,也稱為點讀取
- 單一分割區索引鍵上具有篩選子句的查詢
- 任何屬性上具有等號比較或範圍篩選子句的查詢
- 不含篩選的查詢
需要對所有分割區執行的查詢具有較高的延遲,而且可以取用較高的 RU。 由於每個分割區都會針對所有屬性自動編製索引,因此,在此情況下,就能有效率地從索引中提供查詢。 您可以使用平行處理原則選項,更快速地進行跨越分割區的查詢。
若要深入了解分割區和分割區索引鍵,請參閱在 Azure Cosmos DB 中進行資料分割。
若要了解如何使用 SDK 從 Azure Cosmos DB 取得最佳用戶端效能,請參閱查詢效能祕訣和效能測試。
若要了解如何設定全域散發,並連線至最接近的區域,請參閱 Azure Cosmos DB 全域散發。 當您需要進行多次來回行程或從查詢擷取大型結果集時,網路延遲會對查詢效能產生顯著影響。
您可以使用查詢執行計量來擷取查詢的伺服器執行時間,讓您可以區分查詢執行所花費的時間與網路傳輸所花費的時間。
若要了解編製索引路徑、種類和模式,以及其對執行查詢的影響,請參閱設定編製索引原則。 根據預設,Azure Cosmos DB 會將自動編製索引套用至所有資料,並使用字串和數字的範圍索引,而這對相等查詢有效。 針對高效能插入情節,請考慮排除路徑,以降低每個插入作業的 RU 成本。
您可以使用索引計量來識別用於每個查詢的索引,以及是否有任何可改善查詢效能的遺漏索引。
針對要求診斷 中的每個查詢執行,傳回詳細計量。 這些計量描述查詢執行期間所花費的時間,並啟用進階疑難排解。
深入了解取得查詢計量。
計量 | 單位 | 描述 |
---|---|---|
TotalTime |
milliseconds | 查詢執行總時間 |
DocumentLoadTime |
milliseconds | 載入文件所花費的時間 |
DocumentWriteTime |
milliseconds | 寫入和序列化輸出文件所花費的時間 |
IndexLookupTime |
milliseconds | 實體索引層內所花費的時間 |
QueryPreparationTime |
milliseconds | 準備查詢所花費的時間 |
RuntimeExecutionTime |
milliseconds | 查詢執行階段執行總時間 |
VMExecutionTime |
milliseconds | 執行查詢的查詢執行階段中所花費的時間 |
OutputDocumentCount |
計數 | 結果集中的輸出文件數目 |
OutputDocumentSize |
計數 | 已輸出文件的大小總計 (以位元組為單位) |
RetrievedDocumentCount |
計數 | 擷取的文件總數 |
RetrievedDocumentSize |
bytes | 擷取的文件大小總計,以位元組為單位 |
IndexHitRatio |
比率 [0,1] | 符合篩選的文件數目與所載入文件數目的比率 |
用戶端 SDK 可以在內部進行多個查詢要求,以在每個分割區內提供查詢。 如果結果總數超出項目計數要求上限選項、如果查詢超出分割區的佈建輸送量、如果查詢承載達到每個頁面的大小上限,或者如果查詢達到系統已配置的逾時限制,則用戶端會針對每個分割區進行多次呼叫。 每個部分查詢執行都會傳回該頁面的查詢計量。
以下是一些範例查詢,以及如何解譯從查詢執行傳回的部分計量資訊:
查詢 | 範例計量 | 描述 |
---|---|---|
SELECT TOP 100 * FROM c |
"RetrievedDocumentCount": 101 |
擷取的文件數目為 100 + 1,可符合 TOP 子句。 查詢時間大部分花費在 WriteOutputTime 和 DocumentLoadTime ,因為其為掃描。 |
SELECT TOP 500 * FROM c |
"RetrievedDocumentCount": 501 |
RetrievedDocumentCount 現在較高 (500+1 以符合 TOP 子句)。 |
SELECT * FROM c WHERE c.N = 55 |
"IndexLookupTime": "00:00:00.0009500" |
在 IndexLookupTime 中大約花費了 0.9 毫秒進行索引鍵查閱,因為它會透過 /N/? 進行索引查閱。 |
SELECT * FROM c WHERE c.N > 55 |
"IndexLookupTime": "00:00:00.0017700" |
在範圍索引中,於 IndexLookupTime 中所花費的時間稍微多一點 (1.7 毫秒),因為它會根據 /N/? 進行索引查閱。 |
SELECT TOP 500 c.N FROM c |
"IndexLookupTime": "00:00:00.0017700" |
在 DocumentLoadTime 上所花費的時間與上一個查詢相同,但 DocumentWriteTime 較低,因為我們只要投影一個屬性。 |
SELECT TOP 500 udf.toPercent(c.N) FROM c |
"RuntimeExecutionTime": "00:00:00.2136500" |
在 RuntimeExecutionTime 中大約花費了 213 毫秒,以針對 c.N 的每個值執行 UDF。 |
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') |
"IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" |
在 IndexLookupTime 中針對 /Name/? 大約花費了 0.6 毫秒。 RuntimeExecutionTime 中,大部分的查詢執行時間 (~7 毫秒)。 |
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') |
"IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 |
查詢會當作掃描來執行,因為它使用 LOWER ,而且會傳回 2491 份擷取文件中的 500 份。 |