次の方法で共有

Cosmos DB 中的索引

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
  • tags
    • 0
      • category: clothing
      • type: jacket
    • 1
      • category: outdoor
      • type: winter
  • inventory
    • warehouse: Seattle
    • quantity: 50
  • distributors
    • 0
      • name: Contoso
    • 1
      • name: AdventureWorks

Cosmos DB 中项的树表示关系图。此图显示了 Cosmos DB 中 JSON 项的分层结构,其中包含 ID、名称、标记、库存和分发服务器属性的分支。

请注意数组在树中的编码方式:数组中的每个条目都获取一个中间节点,该节点标有数组中该条目的索引。 例如,第一个条目是 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.property
    
  • JOIN 查询:

    SELECT
      d
    FROM
      container c
    JOIN
      d IN c.properties
    WHERE
      d = 'value'
    

范围索引可用于标量值(字符串或数字)。 新建容器的默认索引策略会对任何字符串或数字强制使用范围索引。

注释

ORDER BY 单个属性排序的子句 始终 需要范围索引,如果它引用的路径没有范围索引,则失败。 同样, ORDER BY 按多个属性排序的查询 始终 需要复合索引。

空间索引

空间 索引支持对地理空间对象(如点、线条、多边形和多多边形)进行高效的查询。 这些查询使用ST_DISTANCEST_WITHINST_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 根据容器中的项数增加

编写查询时,应采用尽可能有效使用索引的筛选谓词。 例如,如果 StartsWithContains 都适合你的用例,应选择 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”的项)将匹配此处标注的路径:

  • tags
    • 1
      • category: outdoor

遍历(搜索)图,突出显示 Cosmos DB 项结构中路径标签/1/类别/户外

由于此查询具有相等筛选器,因此在遍历此树后,我们可以快速识别包含查询结果的索引页。 在这种情况下,查询引擎将读取包含 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 费用就越高。

例如,请考虑两个属性:namewarehouse。 名称基数为 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/quantityinventory/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 查询必须以独占方式依赖索引,因此如果筛选表达式可能返回假正匹配项,查询引擎将采用完全扫描。

具有以下聚合函数的查询必须以独占方式依赖索引,因此评估某些系统函数需要采用完全扫描。