Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
本文逐步讲解一般建议的方法,用于排查 Azure Cosmos DB 中的查询问题。 虽然不应将本文中所述的步骤视为针对潜在查询问题的完全防御方法,但我们在其中包含了最常见的性能提示。 应使用本文作为排查 Azure Cosmos DB for NoSQL 中查询速度慢或成本高的起点。 还可以使用诊断日志来识别速度缓慢或消耗大量吞吐量的查询。 如果使用 Azure Cosmos DB 的用于 MongoDB 的 API,则应使用 Azure Cosmos DB 的用于 MongoDB 的 API 查询故障排除指南
Azure Cosmos DB 中的查询优化被广泛分类,如下所示:
- 可降低查询请求单位 (RU) 费用的优化
- 仅降低延迟的优化
如果降低查询的 RU 费用,通常还会降低延迟。
常见 SDK 问题
阅读本指南之前,考虑与查询引擎无关的常见 SDK 问题将很有帮助。
- 按照这些 SDK 性能提示进行查询。
- 有时,即使未来页上包含结果,查询也可能包含空页, 其原因可能包括:
- SDK 可能正在执行多个网络调用。
- 查询可能需要很长时间来检索文档。
- 所有查询都包含一个继续标记,该标记将允许查询继续进行。 请确保完全耗尽查询。 详细了解如何处理多页结果
获取查询指标
在 Azure Cosmos DB 中优化查询时,第一步始终是获取查询指标。 这些指标也可通过Azure portal获取。 在Data Explorer中运行查询后,Results 选项卡旁边会显示查询指标:
获取查询指标后,将查询的“已检索文档计数”与“输出文档计数”进行比较 。 使用这种比较可以确定要在本文中查看的相关部分。
“已检索文档计数”是查询引擎需要加载的文档数。 “输出文档计数”是查询结果所需的文档数。 如果Retrieved Document Count高于Output Document Count,则查询中至少有一个部分无法使用索引,并且需要进行扫描。
请参阅以下部分,了解适用于你的方案的相关查询优化。
查询的 RU 费用过高
“已检索文档计数”大于“输出文档计数”
“已检索文档计数”约等于“输出文档计数”
查询的 RU 费用可接受,但延迟仍然过高
“已检索文档计数”超过“输出文档计数”的查询
“已检索文档计数”是查询引擎需要加载的文档数。 “输出文档计数”是查询返回的文档数。 如果Retrieved Document Count高于Output Document Count,则查询中至少有一个部分无法使用索引,因此需要进行扫描。
下面是一个不完全由索引提供服务的扫描查询示例:
查询:
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
可以随时将属性添加到索引策略,而不会影响写入或读取可用性。 你可以跟踪索引转换进度。
了解哪些系统函数使用索引
大多数系统函数都使用索引。 下面列出了一些使用索引的常用字符串函数:
- 以...开始
- 包含
- RegexMatch
- 左边
- Substring - 但是仅当在第一个 num_expr 为 0 时
下面是一些在 WHERE 子句中使用时不使用索引且必须加载每个文档的常用系统函数:
| 系统函数 | 优化思路 |
|---|---|
| 上/下 | 不要使用系统函数来规范化数据以进行比较,而是在插入时规范化大小写。 诸如 SELECT * FROM c WHERE UPPER(c.name) = 'BOB' 的查询将变成 SELECT * FROM c WHERE c.name = 'BOB'。 |
| GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks (获取当前日期时间/获取当前时间戳/获取当前滴答数) | 计算查询执行前的当前时间并在 WHERE 子句中使用该字符串值。 |
| 数学函数(非聚合) | 如果需要频繁计算查询中的某个值,请考虑在 JSON 文档中将此值存储为属性。 |
这些系统函数可以使用索引,但在包含聚合的查询中使用时除外:
| 系统函数 | 优化思路 |
|---|---|
| 空间系统函数 | 将查询结果存储在实时具体化视图中 |
在 SELECT 子句中使用时,低效的系统函数不会影响查询使用索引的方式。
改进字符串系统函数执行
对于某些使用索引的系统函数,可以通过将 ORDER BY 子句添加到查询来改进查询执行。
更具体地说,在查询中添加 ORDER BY 时,RU 费用随属性的基数增加而增加的任何系统函数都可能从中受益。 这些查询进行索引扫描,因此,对查询结果进行排序可以提高查询的效率。
此优化可改进以下系统函数的执行:
- StartsWith(其中不区分大小写 = true)
- StringEquals(其中不区分大小写 = true)
- 包含
- RegexMatch
- 以...结尾
例如,考虑下面带有 CONTAINS 的查询。
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")
可以通过添加 ORDER BY 和 (c.name, c.town) 的组合索引来改进查询执行:
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 表达式
多值子查询可以通过在 JOIN 子句中的每个 select-many 表达式后面(而不是所有交叉联接后面)推送谓词,来优化 WHERE 表达式。
请看下面的查询:
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 表达式将在应用任何筛选器之前,对每个匹配文档执行 tags、nutrients 和 servings 数组的所有项的叉积计算。 然后,WHERE 子句将对每个 <c, t, n, s> 元组应用筛选谓词。
例如,如果匹配文档在这三个数组中的每个数组中都具有 10 个项,则该文档将扩展为 1 x 10 x 10 x 10(即 1000)个元组。 在此处使用子查询可帮助在与下一个表达式联接之前,筛选出联接的数组项。
此查询等效于前面的查询,但使用了子查询:
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
假设 tags 数组中只有一个项与筛选器相匹配,而 nutrients 和 servings 数组各有 5 个项。 那么,JOIN 表达式将扩展为 1 x 1 x 5 x 5 = 25 个项,而不是第一个查询中的 1000 个项。
“已检索文档计数”等于“输出文档计数”的查询
如果“已检索文档计数”约等于“输出文档计数”,则查询引擎无需扫描许多不必要的文档 。 对于许多查询(例如,使用 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 吞吐量。 如果预配 1000 RU,则每秒可以运行该查询 200 次。 如果在没有足够的可用吞吐量时尝试运行查询,Azure Cosmos DB 将返回 HTTP 429 错误。 任何 NoSQL SDK 的 API 在等待一小段时间后,将自动重试该查询。 受限制的请求需要花费更长的时间,因此增加预配的吞吐量可以改进查询延迟。 可以在Azure portal的 Metrics 边栏选项卡上观察限制请求总数。
增加 MaxConcurrency
并行查询的方式是并行查询多个分区。 但就查询本身而言,会按顺序提取单个已分区集合中的数据。 因此,如果将 MaxConcurrency 设置为分区数,则最有可能实现查询的最高性能,但前提是所有其他系统条件仍保持不变。 如果不知道分区数,可将 MaxConcurrency(或旧 SDK 版本中的 MaxDegreesOfParallelism)设置为较大的数字。 系统会选择最小值(分区数、用户提供的输入)作为最大并行度。
增加 MaxBufferedItemCount
查询专用于在客户端处理当前一批结果时预提取结果。 预提取可帮助改进查询的总体延迟。 设置 MaxBufferedItemCount 会限制预提取结果的数目。 如果将此值设置为预期返回的结果数(或更大的数目),则查询从预提取中获益的程度将最大。 如果将此值设置为 -1,则系统将自动确定要缓冲的项数。
后续步骤
参阅以下文章,了解有关如何按查询度量 RU、获取执行统计信息以优化查询等信息: