Cosmos DB 是一个与架构无关的数据库,可用于循环访问应用程序,而无需处理架构或索引管理。 这也称为 读取架构,这意味着当将数据写入数据库时,Cosmos DB 不会对数据强制实施架构。 相反,在读取或查询数据时,架构将投影到应用程序中定义的类中。
Microsoft Fabric 中的 Cosmos DB 中的索引旨在提供快速灵活的查询性能,无论数据如何发展。 默认情况下,Cosmos DB 会自动为容器中的所有项为每个属性编制索引,而无需定义任何架构或配置辅助索引。
概念树
每次在容器中存储项时,项的内容都投影为 JSON 文档,然后转换为树表示形式。 此转换意味着,该项的每个属性都在树中以节点的形式呈现。 伪根节点被创建为项的所有第一级属性的父级。 叶节点包含项带有的实际标量值。
例如,请看以下项:
{
"locations": [
{
"country": "Germany",
"city": "Berlin"
},
{
"country": "France",
"city": "Paris"
}
],
"headquarters": {
"country": "Belgium",
"employees": 250
},
"exports": [
{
"city": "Moscow"
},
{
"city": "Athens"
}
]
}
此概念树表示示例 JSON 项:
locations0-
country:Germany -
city:Berlin
-
1-
country:France -
city:Paris
-
headquarters-
country:Belgium -
employees:250
-
exports0-
city:Moscow
-
1-
city:Athens
-
显示具有三个分支的根节点的树图:“位置”、“总部”和“导出”。 “位置”拆分为两个编号节点,每个节点具有两个与位置相关的子节点(“德国或柏林”和“法国或巴黎”)。 “总部”位于“比利时”,并拥有“250”名“雇员”。 “导出”拆分为两个编号节点,每个节点都有一个“城市”子节点(“莫斯科”和“雅典”)。
请注意数组在树中的编码方式:数组中的每个条目都获取一个中间节点,该节点标有数组中该条目的索引。 例如,第一个条目是 0 ,第二个条目是 1。
属性路径
Cosmos DB 将项转换为树,因为它允许系统使用这些树中的路径引用属性。 若要获取属性的路径,可从根节点到该属性来遍历树,并将每个遍历的节点的标签连接起来。
下面是之前描述的示例项中每个属性的路径:
| 路径 | 价值 |
|---|---|
/locations/0/country |
"Germany" |
/locations/0/city |
"Berlin" |
/locations/1/country |
"France" |
/locations/1/city |
"Paris" |
/headquarters/country |
"Belgium" |
/headquarters/employees |
250 |
/exports/0/city |
"Moscow" |
/exports/1/city |
"Athens" |
当写入项时,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 | 无 | 根据容器中的项数增加 |
编写查询时,应采用尽可能有效使用索引的筛选谓词。 例如,如果 StartsWith 或 Contains 都适合你的用例,应选择 StartsWith,因为它将执行精确索引扫描,而不是完全索引扫描。
索引使用情况详细信息
小窍门
本部分介绍有关查询如何使用索引的更多详细信息。 学习如何开始使用 Cosmos DB 时,不需要此级别的详细信息,但为好奇的用户详细记录。 我们将参考在本文档前面分享的示例项:
请考虑以下两个示例项:
[
{
"id": 1,
"locations": [
{ "country": "Germany", "city": "Berlin" },
{ "country": "France", "city": "Paris" }
],
"headquarters": { "country": "Belgium", "employees": 250 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" }
]
},
{
"id": 2,
"locations": [
{ "country": "Ireland", "city": "Dublin" }
],
"headquarters": { "country": "Belgium", "employees": 200 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" },
{ "city": "London" }
]
}
]
Cosmos DB 使用倒排索引。 索引的工作原理是将每个 JSON 路径映射到包含该值的一组项中。 对于容器,项 ID 映射跨多个不同的索引页表示。 以下是包含两个示例项的容器的倒排索引示例示意图:
| 路径 | 价值 | 项标识符列表 |
|---|---|---|
/locations/0/country |
Germany |
[1] |
/locations/0/country |
Ireland |
[2] |
/locations/0/city |
Berlin |
[1] |
/locations/0/city |
Dublin |
[2] |
/locations/1/country |
France |
[1] |
/locations/1/city |
Paris |
[1] |
/headquarters/country |
Belgium |
[1, 2] |
/headquarters/employees |
200 |
[2] |
/headquarters/employees |
250 |
[1] |
倒排索引具有 2 个重要属性:
对于给定路径,值按升序排序。 因此,查询引擎可轻松地从索引中提供
ORDER BY。对于给定路径,查询引擎可扫描一组非重复的可能值,以确定存在结果的索引页。
查询引擎使用倒排索引的方式有以下 4 种:
索引查找
请考虑下列查询:
SELECT
location
FROM
location IN company.locations
WHERE
location.country = 'France'
查询谓词(筛选任何位置的区域为“法国”的项)将匹配此处调用的路径:
locations1-
country:France
-
显示具有三个分支的根节点的树图:“位置”、“总部”和“导出”。 “位置”拆分为两个编号节点,每个节点具有两个与位置相关的子节点(“德国/柏林”和“法国/巴黎”)。 “总部”位于“比利时”,并拥有“250”名“雇员”。 “导出”拆分为两个编号节点,每个节点都有一个“城市”子节点(“莫斯科”和“雅典”)。 突出了“位置”、“1”、“地点”和“法国”的路径。
由于此查询具有相等筛选器,因此在遍历此树后,我们可以快速识别包含查询结果的索引页。 在这种情况下,查询引擎将读取包含项 1 的索引页。 索引查找是使用索引最有效的方式。 通过索引查找,我们只读取必要的索引页,并且只加载查询结果中的项。 因此,无论总数据量是多少,索引查找的查找时间都很短,并且 RU 费用低。
精确索引扫描
请考虑下列查询:
SELECT
*
FROM
company
WHERE
company.headquarters.employees > 200
查询谓词(对具有超过 200 名员工的项进行筛选)可通过 headquarters/employees 路径的精确索引扫描进行评估。 执行精确索引扫描时,查询引擎首先对一组非重复的可能值进行二进制搜索,来查找 200 路径的值 headquarters/employees 的位置。 由于每个路径的值都是按升序排序的,因此查询引擎可轻松执行二进制搜索。 查询引擎找到值 200 后,将开始读取所有剩余的索引页(按升序反向)。
查询引擎可执行二进制搜索来避免扫描不必要的索引页,因此精确索引扫描的延迟和 RU 费用与索引查找操作相当。
扩展索引扫描
请考虑下列查询:
SELECT
*
FROM
company
WHERE
STARTSWITH(company.headquarters.country, "United", true)
查询谓词(筛选总部位于位置以不区分大小写的“United”开头的项目)可以通过扩展headquarters/country路径上的索引扫描来评估。 执行扩展索引扫描的操作具有一些优化,可帮助避免扫描每个索引页,但比精确索引扫描的二进制搜索略贵。
例如,在评估不区分大小写的 StartsWith 时,查询引擎将检查索引中是否有大写值和小写值混用的情况。 此优化使查询引擎能够避免读取大多数索引页。 不同的系统函数具有不同的优化,它们可用于避免读取每个索引页,因此我们可将其大致分类为扩展索引扫描。
完全索引扫描
请考虑下列查询:
SELECT
*
FROM
company
WHERE
CONTAINS(company.headquarters.country, "United")
查询谓词(对总部位于包含“United”的位置的项目进行筛选)可通过 headquarters/country 路径的索引扫描进行评估。 与精确索引扫描不同,完全索引扫描将始终扫描一组非重复的可能值,以确定存在结果的索引页。 在这种情况下,索引上会运行 CONTAINS。 索引扫描的索引查找时间和 RU 费用会随着路径基数的增加而增加。 换句话说,查询引擎需要扫描的可能的非重复值越多,执行完全索引扫描的延迟和 RU 费用就越高。
例如,请考虑两个属性:town 和 country。 town 的基数是 5,000,country 的基数是 200。 下面是两个示例查询,每个查询都有一个系统函数 CONTAINS ,它对 town 属性执行完全索引扫描。 第一个查询使用的请求单位(RU)多于第二个查询,因为城镇基数高于 country。
SELECT
*
FROM
container c
WHERE
CONTAINS(c.town, "Red", false)
SELECT
*
FROM
c
WHERE
CONTAINS(c.country, "States", false)
完全扫描
在某些情况下,查询引擎可能无法使用索引评估查询筛选器。 在这种情况下,为了评估查询筛选器,查询引擎需要从事务存储中加载所有的项。 完全扫描不使用索引,而且其 RU 费用根据总数据大小呈线性增加。 幸运的是,很少有需要完全扫描的操作。
没有定义的矢量索引的矢量搜索查询
如果未定义矢量索引策略并使用 VECTORDISTANCE 子句中的 ORDER BY 系统函数,则此查询将生成完全扫描,并且 RU 费用高于你定义的向量索引策略。 类似地,如果将 VECTORDISTANCE 暴力布尔值设置为 true,并且没有为矢量路径定义 flat 索引,则会进行完全扫描。
具有复杂筛选表达式的查询
在前面的示例中,我们只考虑到具有简单筛选表达式的查询(例如,只具有单个相等或范围筛选器的查询)。 实际上,大多数查询都具有更复杂的筛选表达式。
请考虑下列查询:
SELECT
*
FROM
company
WHERE
company.headquarters.employees = 200 AND CONTAINS(company.headquarters.country, "United")
要执行此查询,查询引擎必须对 headquarters/employees 和 headquarters/country 分别执行索引查找和完全索引扫描。 查询引擎具有内部启发法,用于尽可能高效地评估查询筛选表达式。 在这种情况下,查询引擎通过首先执行索引查找来避免读取不必要的索引页。 例如,如果只有 50 个项与相等筛选器匹配,则查询引擎只需要在包含这 50 个项的索引页上评估 CONTAINS。 无需对整个容器执行完全索引扫描。
标量聚合函数的索引使用率
具有聚合函数的查询必须以独占方式依赖索引才能使用它。
在某些情况下,索引会返回假正。 例如,在 CONTAINS 索引上求值时,索引中的匹配项数可能超过查询结果数。 查询引擎会加载所有索引匹配项,评估已加载的项上的筛选器,并且只返回正确的结果。
对于大多数查询,加载假正索引匹配项不会对索引利用率产生任何显著影响。
例如,考虑以下查询:
SELECT
*
FROM
company
WHERE
CONTAINS(company.headquarters.country, "United")
系统 CONTAINS 函数可能会返回一些误报匹配,因此查询引擎需要验证每个加载的项是否与筛选器表达式匹配。 在此示例中,查询引擎可能只需要加载额外的几个项,因此对索引利用率和 RU 费用的影响很小。
但是,具有聚合函数的查询必须以独占方式依赖索引才能使用它。 例如,考虑使用具有 COUNT 聚合的以下查询:
SELECT
COUNT(1)
FROM
company
WHERE
CONTAINS(company.headquarters.country, "United")
与在第一个示例中一样, CONTAINS 系统函数可能会返回一些误报匹配。 但与 SELECT * 查询不同,COUNT 查询无法通过评估已加载项上的筛选表达式来验证所有索引匹配项。
COUNT 查询必须以独占方式依赖索引,因此如果筛选表达式可能返回假正匹配项,查询引擎将采用完全扫描。
具有以下聚合函数的查询必须以独占方式依赖索引,因此评估某些系统函数需要采用完全扫描。