Cosmos DB 是一个与架构无关的数据库,可用于循环访问应用程序,而无需处理架构或索引管理。 此功能也称为 读取架构,这意味着当写入数据库时,Cosmos DB 不会对数据强制实施架构。 在读取或写入操作期间从数据库反序列化数据时,架构定义在您应用程序中创建的类里。
概念树
每次在容器中存储项时,项的内容都投影为 JSON 文档,然后转换为树表示形式。 此转换意味着,该项的每个属性都在树中以节点的形式呈现。 伪根节点被创建为项的所有第一级属性的父级。 叶节点包含项带有的实际标量值。
例如,请看以下项:
{
"id": "00000000-0000-0000-0000-000000004368",
"name": "Cofaz Jacket",
"tags": [
{ "category": "clothing", "type": "jacket" },
{ "category": "outdoor", "type": "winter" }
],
"inventory": { "warehouse": "Seattle", "quantity": 50 },
"distributors": [
{ "name": "Contoso" },
{ "name": "AdventureWorks" }
]
}
此概念树表示示例 JSON 项:
-
id:00000000-0000-0000-0000-000000004368 -
name:Cofaz Jacket tags0-
category:clothing -
type:jacket
-
1-
category:outdoor -
type:winter
-
inventory-
warehouse:Seattle -
quantity:50
-
distributors0-
name:Contoso
-
1-
name:AdventureWorks
-
请注意数组在树中的编码方式:数组中的每个条目都获取一个中间节点,该节点标有数组中该条目的索引。 例如,第一个条目是 0 ,第二个条目是 1。
属性路径
Cosmos DB 将项转换为树,因为它允许系统使用这些树中的路径引用属性。 若要获取属性的路径,可从根节点到该属性来遍历树,并将每个遍历的节点的标签连接起来。
下面是之前描述的示例项中每个属性的路径:
| 路径 | 价值 |
|---|---|
/id |
"00000000-0000-0000-0000-000000004368" |
/name |
"Cofaz Jacket" |
/tags/0/category |
"clothing" |
/tags/0/type |
"jacket" |
/tags/1/category |
"outdoor" |
/tags/1/type |
"winter" |
/inventory/warehouse |
"Seattle" |
/inventory/quantity |
50 |
/distributors/0/name |
"Contoso" |
/distributors/1/name |
"AdventureWorks" |
当写入项时,Cosmos DB 有效地为每个属性的路径及其对应的值编制索引。
Cosmos DB 中的索引类型
Cosmos DB 目前支持四种类型的索引。
定义索引策略时,可以配置这些索引类型。
范围索引
范围索引基于已排序的树形结构。 范围索引类型用于:
相等查询:
SELECT * FROM container c WHERE c.property = 'value'SELECT * FROM container c WHERE c.property IN ("value1", "value2", "value3")数组元素上的相等匹配
SELECT * FROM container c WHERE ARRAY_CONTAINS(c.tags, "tag1")范围查询:
SELECT * FROM container c WHERE c.property > 0注释
适用于
>、<、>=、<=、!=检查属性是否存在:
SELECT * FROM container c WHERE IS_DEFINED(c.property)字符串系统函数:
SELECT * FROM container c WHERE CONTAINS(c.property, "value")SELECT * FROM container c WHERE STRINGEQUALS(c.property, "value")ORDER BY查询:SELECT * FROM container c ORDER BY c.propertyJOIN查询:SELECT d FROM container c JOIN d IN c.properties WHERE d = 'value'
范围索引可用于标量值(字符串或数字)。 新建容器的默认索引策略会对任何字符串或数字强制使用范围索引。
注释
由 ORDER BY 单个属性排序的子句 始终 需要范围索引,如果它引用的路径没有范围索引,则失败。 同样, ORDER BY 按多个属性排序的查询 始终 需要复合索引。
空间索引
空间 索引支持对地理空间对象(如点、线条、多边形和多多边形)进行高效的查询。 这些查询使用ST_DISTANCE、ST_WITHIN、ST_INTERSECTS关键字。 下面是使用空间索引类型的一些示例:
地理空间距离查询:
SELECT * FROM container c WHERE ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40在查询的地理空间:
SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })地理空间相交查询:
SELECT * FROM container c WHERE ST_INTERSECTS(c.property, { 'type':'Polygon', 'coordinates': [[ [31.8, -5], [32, -5], [31.8, -5] ]] })
空间索引可用于格式正确的 GeoJSON 对象。 目前支持点、线串、多边形和多面。
复合索引
对多个字段执行操作时,组合索引可提高效率。 复合索引类型用于:
对多个属性的
ORDER BY查询:SELECT * FROM container c ORDER BY c.property1, c.property2使用筛选器和
ORDER BY的查询。 如果在ORDER BY子句中添加筛选器属性,则这些查询可使用组合索引。SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2对两个或多个属性进行查询,其中至少有一个属性使用相等筛选器:
SELECT * FROM container c WHERE c.property1 = 'value' AND c.property2 > 'value'
只要一个筛选器谓词使用其中一种索引类型,查询引擎就会先评估该索引类型,然后再扫描其余部分。 例如,如果有 SQL 查询,例如 SELECT * FROM c WHERE c.department = "Information Technology" and CONTAINS(c.team, "Pilot"):
此查询首先对使用索引的
department = "Information Technology"条目应用筛选器。 然后,它会通过后续管道处理所有department = "Information Technology"条目,以进行CONTAINS筛选器谓词的评估。使用执行完全扫描的函数时,可以加快查询速度并避免对容器的全面扫描
CONTAINS。 可以添加更多使用索引的筛选器谓词来加快这些查询的速度。 筛选子句的顺序并不重要。 查询引擎将确定哪些谓词更具选择性,并相应地运行查询。
矢量索引
矢量索引可在使用 系统函数执行矢量搜索时提高效率VECTORDISTANCE。 使用向量索引时,矢量搜索的延迟较低、吞吐量更高,RU 消耗量更少。 Cosmos DB 支持大小在 4,096 维以下的任何矢量嵌入(文本、图像、多模式等)。
ORDER BY矢量搜索查询:SELECT TOP 10 * FROM container c ORDER BY VECTORDISTANCE(c.vector1, c.vector2)矢量搜索查询中相似性分数的投影:
SELECT TOP 10 c.name, VECTORDISTANCE(c.vector1, c.vector2) AS score FROM container c ORDER BY VECTORDISTANCE(c.vector1, c.vector2)基于相似性分数的范围筛选器。
SELECT TOP 10 * FROM container c WHERE VECTORDISTANCE(c.vector1, c.vector2) > 0.8 ORDER BY VECTORDISTANCE(c.vector1, c.vector2)
重要
创建后,矢量策略和矢量索引是不可变的。 若要进行更改,请创建新的集合。
查询如何使用索引
查询引擎可采用 5 种方式来评估查询筛选器,按效率从高到低排序:
- 索引查找
- 精确索引扫描
- 扩展索引扫描
- 完全索引扫描
- 完全扫描
索引属性路径时,查询引擎会尽可能高效地自动使用索引。 除了索引新的属性路径外,无需配置任何内容即可优化查询使用索引的方式。 查询的请求单位 (RU) 费用是索引使用量的 RU 费用与加载项的 RU 费用之和。
下表总结了 Cosmos DB 中使用索引的不同方式:
| Lookup 类型 | Description | 常见示例 | 从索引使用情况收取费用 | 从事务数据存储加载项的费用 |
|---|---|---|---|---|
| 索引查找 | 只读取所需的索引值,并且只从事务数据存储中加载匹配项 | 相等筛选器,IN | 每个相等筛选器的费用相同 | 根据查询结果中的项数增加 |
| 精确索引扫描 | 索引值的二进制搜索,并且只从事务数据存储中加载匹配项 | 范围比较(>、<、<= 或 >=),StartsWith | 与索引查找相比,根据索引属性的基数略有增加 | 根据查询结果中的项数增加 |
| 扩展索引扫描 | 索引值的优化搜索(但比二进制文搜索的效率低),并且只从事物数据存储中加载匹配项 | StartsWith(不区分大小写的),StringEquals(不区分大小写) | 根据索引属性的基数略有增加 | 根据查询结果中的项数增加 |
| 完整索引扫描 | 读取一组非重复的索引值,并且只从事务数据存储中加载匹配项 | Contains、EndsWith、RegexMatch、LIKE | 根据索引属性的基数呈线性增加 | 根据查询结果中的项数增加 |
| 完全扫描 | 从事务数据存储加载所有项 | Upper、Lower | N/A | 根据容器中的项数增加 |
编写查询时,应采用尽可能有效使用索引的筛选谓词。 例如,如果 StartsWith 或 Contains 都适合你的用例,应选择 StartsWith,因为它将执行精确索引扫描,而不是完全索引扫描。
索引使用情况详细信息
小窍门
本部分介绍有关查询如何使用索引的更多详细信息。 学习如何开始使用 Cosmos DB 时,不需要此级别的详细信息,但为好奇的用户详细记录。 我们将参考在本文档前面分享的示例项:
请考虑以下两个示例项:
[
{
"id": "00000000-0000-0000-0000-000000004368",
"name": "Cofaz Jacket",
"tags": [
{ "category": "clothing", "type": "jacket" },
{ "category": "outdoor", "type": "winter" }
],
"inventory": { "warehouse": "Seattle", "quantity": 50 },
"distributors": [
{ "name": "Contoso" },
{ "name": "AdventureWorks" }
]
},
{
"id": "00000000-0000-0000-0000-000000004002",
"name": "Potana bike",
"tags": [
{ "category": "cycling", "type": "mountain" }
],
"inventory": { "warehouse": "Seattle", "quantity": 30 },
"distributors": [
{ "name": "Contoso" },
{ "name": "Fabrikam" },
{ "name": "Northwind" }
]
}
]
Cosmos DB 使用倒排索引。 索引的工作原理是将每个 JSON 路径映射到包含该值的一组项中。 对于容器,项 ID 映射跨多个不同的索引页表示。 以下是包含两个示例项的容器的倒排索引示例示意图:
| 路径 | 价值 | 项标识符列表 |
|---|---|---|
/tags/0/category |
clothing |
[00000000-0000-0000-0000-000000004368] |
/tags/0/category |
cycling |
[00000000-0000-0000-0000-000000004002] |
/tags/0/type |
jacket |
[00000000-0000-0000-0000-000000004368] |
/tags/0/type |
mountain |
[00000000-0000-0000-0000-000000004002] |
/tags/1/category |
outdoor |
[00000000-0000-0000-0000-000000004368] |
/tags/1/type |
winter |
[00000000-0000-0000-0000-000000004368] |
/inventory/warehouse |
Seattle |
[00000000-0000-0000-0000-000000004368, 00000000-0000-0000-0000-000000004002] |
/inventory/quantity |
30 |
[00000000-0000-0000-0000-000000004002] |
/inventory/quantity |
50 |
[00000000-0000-0000-0000-0000-000000004001] |
倒排索引具有 2 个重要属性:
对于给定路径,值按升序排序。 因此,查询引擎可轻松地从索引中提供
ORDER BY。对于给定路径,查询引擎可扫描一组非重复的可能值,以确定存在结果的索引页。
查询引擎使用倒排索引的方式有以下 4 种:
索引查找
请考虑下列查询:
SELECT
tag
FROM
tag IN product.tags
WHERE
tag.category = 'outdoor'
查询谓词(筛选标记类别为“outdoor”的项)将匹配此处标注的路径:
tags1-
category:outdoor
-
由于此查询具有相等筛选器,因此在遍历此树后,我们可以快速识别包含查询结果的索引页。 在这种情况下,查询引擎将读取包含 Item 00000000-0000-0000-0000-000000004368的索引页。 索引查找是使用索引最有效的方式。 通过索引查找,我们只读取必要的索引页,并且只加载查询结果中的项。 因此,无论总数据量是多少,索引查找的查找时间都很短,并且 RU 费用低。
精确索引扫描
请考虑下列查询:
SELECT
*
FROM
product
WHERE
product.inventory.quantity > 30
通过对inventory/quantity路径的精确索引扫描,可以评估查询谓词(筛选库存中项目数量超过30的项)。 执行精确索引扫描时,查询引擎首先对一组非重复的可能值进行二进制搜索,来查找 30 路径的值 inventory/quantity 的位置。 由于每个路径的值都是按升序排序的,因此查询引擎可轻松执行二进制搜索。 查询引擎找到值 30 后,将开始读取所有剩余的索引页(按升序反向)。
查询引擎可执行二进制搜索来避免扫描不必要的索引页,因此精确索引扫描的延迟和 RU 费用与索引查找操作相当。
扩展索引扫描
请考虑下列查询:
SELECT
*
FROM
product
WHERE
STARTSWITH(product.inventory.warehouse, "Sea", true)
查询谓词(筛选仓库中库存以不区分大小写的“Sea”开头的项目)可以使用路径的 inventory/warehouse 扩展索引扫描进行评估。 执行扩展索引扫描的操作具有一些优化,可帮助避免扫描每个索引页,但比精确索引扫描的二进制搜索略贵。
例如,在评估不区分大小写的 StartsWith 时,查询引擎将检查索引中是否有大写值和小写值混用的情况。 此优化使查询引擎能够避免读取大多数索引页。 不同的系统函数具有不同的优化,它们可用于避免读取每个索引页,因此我们可将其大致分类为扩展索引扫描。
完全索引扫描
请考虑下列查询:
SELECT
*
FROM
product
WHERE
CONTAINS(product.inventory.warehouse, "eat")
查询谓词(在包含“eat”的仓库中筛选有库存的项目)可以通过对 inventory/warehouse 路径进行索引扫描来评估。 与精确索引扫描不同,完全索引扫描将始终扫描一组非重复的可能值,以确定存在结果的索引页。 在这种情况下,索引上会运行 CONTAINS。 索引扫描的索引查找时间和 RU 费用会随着路径基数的增加而增加。 换句话说,查询引擎需要扫描的可能的非重复值越多,执行完全索引扫描的延迟和 RU 费用就越高。
例如,请考虑两个属性:name 和 warehouse。 名称基数为 5,000,warehouse 的基数为 200。 下面是两个示例查询,每个查询都有一个系统函数 CONTAINS ,它对 name 属性执行完全索引扫描。 第一个查询使用的请求单位数(RU)多于第二个查询,因为名称基数高于 warehouse。
SELECT
*
FROM
container c
WHERE
CONTAINS(c.name, "Pack", false)
SELECT
*
FROM
c
WHERE
CONTAINS(c.inventory.warehouse, "Sea", false)
完全扫描
在某些情况下,查询引擎可能无法使用索引评估查询筛选器。 在这种情况下,为了评估查询筛选器,查询引擎需要从事务存储中加载所有的项。 完全扫描不使用索引,而且其 RU 费用根据总数据大小呈线性增加。 幸运的是,很少有需要完全扫描的操作。
没有定义的矢量索引的矢量搜索查询
如果未定义矢量索引策略并使用 VECTORDISTANCE 子句中的 ORDER BY 系统函数,则此查询将生成完全扫描,并且 RU 费用高于你定义的向量索引策略。 类似地,如果将 VECTORDISTANCE 暴力布尔值设置为 true,并且没有为矢量路径定义 flat 索引,则会进行完全扫描。
具有复杂筛选表达式的查询
在前面的示例中,我们只考虑到具有简单筛选表达式的查询(例如,只具有单个相等或范围筛选器的查询)。 实际上,大多数查询都具有更复杂的筛选表达式。
请考虑下列查询:
SELECT
*
FROM
product
WHERE
product.inventory.quantity = 50 AND CONTAINS(product.inventory.warehouse, "Sea")
要执行此查询,查询引擎必须对 inventory/quantity 和 inventory/warehouse 分别执行索引查找和完全索引扫描。 查询引擎具有内部启发法,用于尽可能高效地评估查询筛选表达式。 在这种情况下,查询引擎通过首先执行索引查找来避免读取不必要的索引页。 例如,如果只有 50 个项与相等筛选器匹配,则查询引擎只需要在包含这 50 个项的索引页上评估 CONTAINS。 无需对整个容器执行完全索引扫描。
标量聚合函数的索引使用率
具有聚合函数的查询必须以独占方式依赖索引才能使用它。
在某些情况下,索引会返回假正。 例如,在 CONTAINS 索引上求值时,索引中的匹配项数可能超过查询结果数。 查询引擎会加载所有索引匹配项,评估已加载的项上的筛选器,并且只返回正确的结果。
对于大多数查询,加载假正索引匹配项不会对索引利用率产生任何显著影响。
例如,考虑以下查询:
SELECT
*
FROM
product
WHERE
CONTAINS(product.inventory.warehouse, "Sea")
系统 CONTAINS 函数可能会返回一些误报匹配,因此查询引擎需要验证每个加载的项是否与筛选器表达式匹配。 在此示例中,查询引擎可能只需要加载额外的几个项,因此对索引利用率和 RU 费用的影响很小。
但是,具有聚合函数的查询必须以独占方式依赖索引才能使用它。 例如,考虑使用具有 COUNT 聚合的以下查询:
SELECT
COUNT(1)
FROM
product
WHERE
CONTAINS(product.inventory.warehouse, "Sea")
与第一个示例类似,CONTAINS 系统函数可能返回一些假正匹配项。 但与 SELECT * 查询不同,COUNT 查询无法通过评估已加载项上的筛选表达式来验证所有索引匹配项。
COUNT 查询必须以独占方式依赖索引,因此如果筛选表达式可能返回假正匹配项,查询引擎将采用完全扫描。
具有以下聚合函数的查询必须以独占方式依赖索引,因此评估某些系统函数需要采用完全扫描。