适用范围: NoSQL
Azure Cosmos DB 是一个快速、弹性的分布式数据库,可以在提供有保证的延迟与吞吐量级别的情况下无缝缩放。 凭借 Azure Cosmos DB,无需对体系结构进行重大更改或编写复杂的代码即可缩放数据库。 扩展和缩减操作就像执行单个 API 调用一样简单。 若要了解详细信息,请参阅预配容器吞吐量或预配数据库吞吐量。
若要执行查询,需要生成一个查询计划。 向 Azure Cosmos DB 网关发出的网络请求会添加到查询作的延迟。 可通过两种方式消除此请求并降低查询操作的延迟:
Azure Cosmos DB NoSQL 具有一种叫做 Optimistic Direct Execution (ODE) 的优化,可用来提高某些 NoSQL 查询的效率。 具体而言,不需要分发的查询包括可在单个物理分区上执行的查询或具有不需要分页的响应的查询。 不需要分发的查询可以放心地跳过某些进程,例如客户端查询计划生成和查询重写,从而降低查询延迟和请求单位(RU)成本。 如果你在请求或查询本身中指定分区键(或只有一个物理分区),并且查询结果不需要分页,则 ODE 可以改进查询。
备注
ODE 为不需要分发的查询提供改进的性能,不应与 直接模式混淆,这是将应用程序连接到后端副本的路径。
ODE 现在在 .NET SDK 版本 3.38.0 及更高版本中可用。 执行查询并在请求或查询本身中指定分区键,或者数据库只有一个物理分区时,查询执行可以使用 ODE 的优势。 若要启用 ODE,请设置为 EnableOptimisticDirectExecution
QueryRequestOptions 中的 true。
具有 GROUP BY、 ORDER BY、 DISTINCT 和聚合函数(例如 sum、mean、min 和 max)的单分区查询可以从使用 ODE 中获益匪浅。 但是,在查询面向多个分区或仍然需要分页的情况下,查询响应和 RU 成本的延迟可能高于不使用 ODE 的情况。 因此,使用 ODE 时,应:
- 在调用或查询本身中指定分区键。
- 确保数据大小未增大并导致分区拆分。
- 确保查询结果不需要分页即可获得 ODE 的全部优势。
下面是可从 ODE 中受益的简单单分区查询的几个示例:
- SELECT * FROM r
- SELECT * FROM r WHERE r.pk == "value"
- SELECT * FROM r WHERE r.id > 5
- SELECT r.id FROM r JOIN id IN r.id
- SELECT TOP 5 r.id FROM r ORDER BY r.id
- SELECT * FROM r WHERE r.id > 5 OFFSET 5 LIMIT 3
在某些情况下,如果数据项的数量随着时间的推移而增加,并且 Azure Cosmos DB 数据库 拆分分区,则单个分区查询仍可能需要分布。 可能出现这种情况的查询示例包括:
- SELECT Count(r.id) AS count_a FROM r
- SELECT DISTINCT r.id FROM r
- SELECT Max(r.a) as min_a FROM r
- SELECT Avg(r.a) as min_a FROM r
- SELECT Sum(r.a) as sum_a FROM r WHERE r.a > 0
即使针对单个分区,某些复杂查询也始终需要分发。 此类查询的示例包括:
- SELECT Sum(id) as sum_id FROM r JOIN id IN r.id
- SELECT DISTINCT r.id FROM r GROUP BY r.id
- SELECT DISTINCT r.id, Sum(r.id) as sum_a FROM r GROUP BY r.id
- SELECT Count(1) FROM (SELECT DISTINCT r.id FROM root r)
- SELECT Avg(1) AS avg FROM root r
请务必注意,ODE 可能并不总是检索查询计划,因此,无法禁止或关闭不受支持的查询。 例如,分区拆分后,此类查询不再符合 ODE 的条件,因此不会运行,因为客户端查询计划评估会阻止这些查询。 若要确保兼容性/服务连续性,必须确保仅将 ODE 用于在没有 ODE 的方案中完全支持的查询(即会在常规多分区情况中执行并生成正确结果的查询)。
备注
使用 ODE 可能会导致生成新类型的延续令牌。 旧版 SDK 无法通过设计识别此类令牌,这可能会导致格式不正确的延续令牌异常。 如果有一种方案,较旧的 SDK 使用从较新的 SDK 生成的令牌,我们建议采用两个步骤方法来升级:
- 升级到新的 SDK 并禁用 ODE,这两者同时作为单个部署的一部分。 等待所有节点升级。
- 若要禁用 ODE,请在 QueryRequestOptions 中设置为
EnableOptimisticDirectExecution
false。 - 在所有节点的第二个部署过程中启用 ODE。
SQL SDK 包括用于在本地分析和优化查询的本机 ServiceInterop.dll 。 ServiceInterop.dll 仅在 Windows x64 平台上受支持。 以下类型的应用程序默认使用 32 位主机处理。 若要将主机处理更改为 64 位处理,请根据应用程序的类型执行以下步骤:
对于可执行应用程序,可以在“项目属性”窗口中的“版本”选项卡上,通过将平台目标设置为“x64”来更改主机处理。
对于基于 VSTest 的测试项目,可以通过在 Visual Studio“测试”菜单中选择“测试”“测试设置”>“默认处理器体系结构为 X64”,来更改主机处理。
对于本地部署的 ASP.NET Web 应用程序,可以通过在“工具”“选项”“项目和解决方案”>“Web 项目”下选择“对网站和项目使用 IIS Express 的 64 位版”,来更改主机处理。
对于部署在 Azure 上的 ASP.NET Web 应用程序,可以通过在 Azure 门户上的“应用程序设置”中选择“64 位”平台,来更改主机处理。
备注
新的 Visual Studio 项目默认设置为“任何 CPU”。 我们建议将项目设置为“x64”,使其不会切换到“x86”。 如果添加了仅限 x86 的依赖项,则设置为“任何 CPU”的项目可以轻松切换到“x86”。
ServiceInterop.dll 需要位于要从中执行 SDK DLL 的文件夹中。 仅当你手动复制 DLL 或使用自定义的生成/部署系统时,此位置才是一个考虑因素。
对于通过设置 PartitionKey 属性来 QueryRequestOptions
定位分区键且不包含聚合(包括 Distinct、 DCount 或 Group By)的查询。 在此示例中,/state
的分区键字段已根据值 Washington
进行筛选。
using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
"SELECT * FROM c WHERE c.city = 'Seattle' AND c.state = 'Shanghai'"
{
// ...
}
(可选)可以将分区键作为请求选项对象的一部分提供。
using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
"SELECT * FROM c WHERE c.city = 'Seattle'",
requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
// ...
}
重要
在运行非 Windows OS(如 Linux 和 macOS)的客户端上,分区键应 始终 在请求选项对象中指定。
备注
跨分区查询需要 SDK 访问所有现有分区以检查结果。 容器的物理分区越多,查询速度就可能越慢。
当所有查询结果已由当前组件消耗时,你不需要为每个页面重新创建带有延续的迭代器。 除非分页由另一个调用组件控制,否则请始终优先采用完全清空查询的做法:
using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
"SELECT * FROM c WHERE c.city = 'Seattle'",
requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
while (feedIterator.HasMoreResults)
{
foreach(MyItem document in await feedIterator.ReadNextAsync())
{
// Iterate through documents
}
}
}
对于查询,优化 中的 QueryRequestOptions
属性来确定适合你的应用程序的最佳配置,尤其是当执行跨分区查询时(没有针对分区键值的筛选器)。
MaxConcurrency
控制并行任务的最大数目,即要并行访问的最大分区数。 将值设置为 -1 可让 SDK 决定最佳并发性。
using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
"SELECT * FROM c WHERE c.city = 'Seattle'",
requestOptions: new QueryRequestOptions() {
PartitionKey = new PartitionKey("Washington"),
MaxConcurrency = -1 }))
{
// ...
}
假设:
- D = 默认的最大并行任务数(= 客户端计算机中的处理器总数)
- P = 用户定义的最大并行任务数
- N = 为响应查询需要访问的分区数
以下是并行查询对不同 P 值的行为方式的影响。
- (P == 0) => 串行模式
- (P == 1) => 一个任务的最大数
- (P > 1) => Min (P, N) 并行任务数
- (P < 1) => Min (N, D) 并行任务数
发出 SQL 查询时,如果结果集太大,则结果将以分段形式返回。
备注
MaxItemCount
属性不应仅用于分页目的。 它的主要用途是通过减少单个页面中返回的最大项数来提高查询性能。
也可以使用提供的 Azure Cosmos DB SDK 设置页面大小。
中的 QueryRequestOptions
属性允许你设置要在枚举操作中返回的最大项数。 当设置为 -1 时MaxItemCount
,SDK 会根据文档大小自动找到最佳值。 例如:
using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
"SELECT * FROM c WHERE c.city = 'Seattle'",
requestOptions: new QueryRequestOptions() {
PartitionKey = new PartitionKey("Washington"),
MaxItemCount = 1000}))
{
// ...
}
执行查询时,结果数据在 TCP 数据包中发送。 如果为 MaxItemCount
指定的值太低,则在 TCP 数据包中发送数据所需的往返次数很高,这会影响性能。 因此,如果不确定要为 MaxItemCount
属性设置的值,最好将其设置为 -1 ,让 SDK 选择默认值。
并行查询旨在预提取结果,而客户端正在处理当前批结果。 此预提取有助于改善查询的总体延迟。
MaxBufferedItemCount 属性限制QueryRequestOptions
预提取的结果数。 设置为 MaxBufferedItemCount
返回的结果的预期数(或更高的数字),以允许查询从预提取中获得最大好处。 如果将此值设置为 -1,系统会自动确定要缓冲的项数。
using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
"SELECT * FROM c WHERE c.city = 'Seattle'",
requestOptions: new QueryRequestOptions() {
PartitionKey = new PartitionKey("Washington"),
MaxBufferedItemCount = -1}))
{
// ...
}
无论并行度如何,预提取的工作方式都相同,并且所有分区中的数据都有单个缓冲区。
若要详细了解如何使用 .NET SDK 提高性能,请参阅:
若要执行查询,需要生成一个查询计划。 向 Azure Cosmos DB 网关发出的网络请求会添加到查询作的延迟。
对于范围限定为单个分区的查询,查询计划将在客户端上缓存。 这样,在首次调用后,就不需要调用网关来检索查询计划。 缓存的查询计划的键是 SQL 查询字符串。 需要确保查询已参数化。 否则,查询计划缓存查找通常是缓存未命中,因为查询字符串在调用中不太可能相同。 对于 Java SDK 4.20.0 和更高版本以及 Spring Data Azure Cosmos DB SDK 3.13.0 和更高版本,默认启用查询计划缓存。
对于范围限定为 setPartitionKey 的 CosmosQueryRequestOptions
分区键且不包含聚合(包括 Distinct、 DCount 或 Group By)的参数化查询,可以避免查询计划:
CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
ArrayList<SqlParameter> paramList = new ArrayList<SqlParameter>();
paramList.add(new SqlParameter("@city", "Seattle"));
SqlQuerySpec querySpec = new SqlQuerySpec(
"SELECT * FROM c WHERE c.city = @city",
paramList);
// Sync API
CosmosPagedIterable<MyItem> filteredItems =
container.queryItems(querySpec, options, MyItem.class);
// Async API
CosmosPagedFlux<MyItem> filteredItems =
asyncContainer.queryItems(querySpec, options, MyItem.class);
备注
跨分区查询需要 SDK 访问所有现有分区以检查结果。 容器的物理分区越多,查询速度就可能越慢。
并行查询的方式是并行查询多个分区。 但对于查询,将按顺序提取单个已分区容器中的数据。 因此,请使用 上的 CosmosQueryRequestOptions
将值设置为你的分区数。 如果你不知道分区数,可以使用 setMaxDegreeOfParallelism
设置一个较大的数字,系统会选择最小值(分区数、用户输入)作为最大并行度。 将值设置为 -1 可让 SDK 决定最佳并发性。
请务必注意,如果数据在查询的所有分区之间均匀分布,并行查询将产生最佳优势。 如果分区容器的分区方式使查询返回的所有或大部分数据集中在几个分区(最坏情况下为一个分区),则查询的性能会下降。
CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxDegreeOfParallelism(-1);
// Define the query
// Sync API
CosmosPagedIterable<MyItem> filteredItems =
container.queryItems(querySpec, options, MyItem.class);
// Async API
CosmosPagedFlux<MyItem> filteredItems =
asyncContainer.queryItems(querySpec, options, MyItem.class);
假设:
- D = 默认的最大并行任务数(= 客户端计算机中的处理器总数)
- P = 用户定义的最大并行任务数
- N = 为响应查询需要访问的分区数
以下是并行查询对不同 P 值的行为方式的影响:
- (P == 0) => 串行模式
- (P == 1) => 一个任务的最大数
- (P > 1) => Min (P, N) 并行任务数
- (P == -1) => Min (N, D) 并行任务数
发出 SQL 查询时,如果结果集太大,则结果将以分段形式返回。 默认情况下,以包括 100 个项的块或 4 MB 大小的块返回结果(以先达到的限制为准)。 增加页面大小可减少所需的往返次数,并提高返回 100 个项的查询的性能。 如果你不确定要设置多大的值,则 1000 通常是一个不错的选择。 内存消耗随着页面大小的增加而增加,因此,如果工作负荷对内存敏感,请考虑较低的值。
可将 pageSize
中的 iterableByPage()
参数用于同步 API,将 byPage()
用于异步 API,以定义页面大小:
// Sync API
Iterable<FeedResponse<MyItem>> filteredItemsAsPages =
container.queryItems(querySpec, options, MyItem.class).iterableByPage(continuationToken,pageSize);
for (FeedResponse<MyItem> page : filteredItemsAsPages) {
for (MyItem item : page.getResults()) {
//...
}
}
// Async API
Flux<FeedResponse<MyItem>> filteredItemsAsPages =
asyncContainer.queryItems(querySpec, options, MyItem.class).byPage(continuationToken,pageSize);
filteredItemsAsPages.map(page -> {
for (MyItem item : page.getResults()) {
//...
}
}).subscribe();
并行查询旨在预提取结果,而客户端正在处理当前批结果。 预提取有助于改善查询的总体延迟。
setMaxBufferedItemCount 限制 CosmosQueryRequestOptions
预提取的结果数。 若要最大化预提取,请将数字设置为 maxBufferedItemCount
大于 pageSize
(注意:这也会导致内存消耗过高)。 若要最小化预提取,请将 maxBufferedItemCount
等于 pageSize
. 如果将此值设置为 0,系统会自动确定要缓冲的项数。
CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxBufferedItemCount(-1);
// Define the query
// Sync API
CosmosPagedIterable<MyItem> filteredItems =
container.queryItems(querySpec, options, MyItem.class);
// Async API
CosmosPagedFlux<MyItem> filteredItems =
asyncContainer.queryItems(querySpec, options, MyItem.class);
无论并行度如何,预提取的工作方式都相同,并且所有分区中的数据都有单个缓冲区。
若要详细了解如何使用 Java SDK 提高性能,请参阅:
若要执行查询,需要生成一个查询计划。 向 Azure Cosmos DB 网关发出的网络请求会添加到查询作的延迟。 有一种方法可以删除此请求并降低单个分区查询作的延迟。 对于单个分区查询,请指定项的分区键值,并将其作为 partition_key 参数传递:
items = container.query_items(
query="SELECT * FROM r where r.city = 'Seattle'",
partition_key="Washington"
)
发出 SQL 查询时,如果结果集太大,则结果将以分段形式返回。 max_item_count 允许你设置要在枚举操作中返回的最大项数。
items = container.query_items(
query="SELECT * FROM r where r.city = 'Seattle'",
partition_key="Washington",
max_item_count=1000
)
若要详细了解如何使用 Python SDK for API for NoSQL,请执行以下操作:
若要执行查询,需要生成一个查询计划。 向 Azure Cosmos DB 网关发出的网络请求会添加到查询作的延迟。 有一种方法可以删除此请求并降低单个分区查询作的延迟。 对于单分区查询,可以通过两种方式将查询范围限定为单个分区。
使用参数化查询表达式,并在查询语句中指定分区键。 查询以编程方式组合为 SELECT * FROM todo t WHERE t.partitionKey = 'Bikes, Touring Bikes'
:
// find all items with same categoryId (partitionKey)
const querySpec = {
query: "select * from products p where p.categoryId=@categoryId",
parameters: [
{
name: "@categoryId",
value: "Bikes, Touring Bikes"
}
]
};
// Get items
const { resources } = await container.items.query(querySpec).fetchAll();
for (const item of resources) {
console.log(`${item.id}: ${item.name}, ${item.sku}`);
}
或者,在 中指定 FeedOptions
并将其作为参数传递:
const querySpec = {
query: "select * from products p"
};
const { resources } = await container.items.query(querySpec, { partitionKey: "Bikes, Touring Bikes" }).fetchAll();
for (const item of resources) {
console.log(`${item.id}: ${item.name}, ${item.sku}`);
}
发出 SQL 查询时,如果结果集太大,则结果将以分段形式返回。 max_item_count 允许你设置要在枚举操作中返回的最大项数。
const querySpec = {
query: "select * from products p where p.categoryId=@categoryId",
parameters: [
{
name: "@categoryId",
value: items[2].categoryId
}
]
};
const { resources } = await container.items.query(querySpec, { maxItemCount: 1000 }).fetchAll();
for (const item of resources) {
console.log(`${item.id}: ${item.name}, ${item.sku}`);
}
对于 Cosmos DB JS SDK 版本 4.3.0 及更高版本, enableQueryControl
引入了标志,它可以更好地控制查询执行,从而更灵活地管理请求单元(RU)消耗。
默认情况下,enableQueryControl
被设置为 false
,并且 SDK 保证每次 fetchNext
调用都会返回 maxItemCount
个结果,前提是后端中存在足够的结果。 但为了满足保证的结果计数,SDK 可能会在单个 fetchNext
迭代中多次查询后端分区。 这有时可能会导致不可预知的延迟和 RU 耗尽,无需用户控制,尤其是在结果分散在分区之间时。
如果 enableQueryControl
设置为 true
,则每个 fetchNext
调用现在都会查询 maxDegreeOfParallelism
物理分区。 如果未找到任何结果或尚未准备好提供结果,SDK 将返回空页,而不是继续搜索后台的所有分区。 这样,用户就可以更精细地控制其查询执行,并具有可预测的延迟和精细的 RU 消耗数据。
const options = {
enableQueryControl: true, // Flag to enable new query pipeline.
maxItemCount: 100,
maxDegreeOfParallelism: 6
};
const querySpec = {
query: "select * from products p where p.categoryId=@categoryId",
parameters: [
{
name: "@categoryId",
value: items[2].categoryId
}
]
};
const queryIterator = container.items.query(querySpec, options);
// use this iterator to fetch results.
若要详细了解如何使用 Node.js SDK for API for NoSQL,请执行以下操作: