Cloud Firestore 中的索引类型

索引是决定数据库性能的一个重要因素。一本书中的索引可以将书中的主题与页码相对应,与此类似,数据库中的索引可以将数据库中的内容映射到对应的位置。当您向数据库发送一个查询请求时,数据库可以使用索引来快速查找所需内容的位置。

本文介绍了 Cloud Firestore 使用的两种索引类型:单字段索引复合索引

每个查询都使用索引

如果一个查询没有索引,大部分数据库需要一项一项地抓取其中的内容,这个过程非常缓慢,且数据库越大,速度越慢。Cloud Firestore 为所有查询使用索引,以确保较高的查询性能。正因于此,查询的性能取决于结果集的大小,而不是数据库中的条目数量。

减少索引管理,专注应用开发

Cloud Firestore 的功能可减少管理索引所需的时间,它会自动为您创建最基本的查询所需的索引。在您使用并测试应用的过程中,Cloud Firestore 会帮助您识别并创建您的应用所需的其他索引

索引类型

Cloud Firestore 使用了两种索引类型:单字段索引和复合索引。这两种类型的索引都是借助它们编入索引的字段和每个字段的索引模式来实现不重复定义。

索引模式

定义索引时,您可以为每个已编入索引的字段选择索引模式。索引模式有三个选项:

索引模式 说明
升序 arrow_upward 支持在该字段上使用 <<===>=> 查询子句,并且支持根据此字段值按升序对结果进行排序。
降序 arrow_downward 支持在该字段上使用 <<===>=> 查询子句,并且支持根据此字段值按降序对结果进行排序。
数组包含 支持在该字段上使用 array_contains 查询子句。

因此,举例来说,字段 foo 上采用升序模式的索引与字段 foo 上采用降序模式的索引不是同一个索引。

单字段索引

单字段索引会为一个集合中包含某个特定字段的所有文档存储一个映射,并按顺序排序。单字段索引中的每个条目会记录一个文档中一个特定字段的值,以及该文档在数据库中的位置。Cloud Firestore 会自动为文档中每个字段维护一个单字段索引。

自动编入索引

默认情况下,Cloud Firestore 会自动为文档中的每个字段和映射中的每个子字段维护一个索引。Cloud Firestore 会为自动创建的单字段索引使用以下设置:

  • 对于每个非数组和非映射字段,Cloud Firestore 会定义两个单字段索引,一个采用升序模式,另一个采用降序模式。

  • 对于每个映射字段,Cloud Firestore 会为映射中的每个非数组和非映射子字段创建一个以升序排序的索引和一个以降序排序的索引。

  • 对于文档中的每个数组字段,Cloud Firestore 会创建并维护一个“数组包含”索引。

单字段索引例外项

您可以创建单字段索引例外项,不让系统将某个字段自动编入索引。索引例外项会覆盖适用于整个数据库的自动索引设置。对于例外项的适用情况,请参阅索引最佳做法

要创建和管理单字段索引例外项,请参阅在 Cloud Firestore 中管理索引

单字段索引支持的查询示例

由于 Cloud Firestore 会自动为您创建这类索引,因此您的应用可以快速执行最基本的数据库查询。您可以使用单字段索引执行基于字段值和比较运算符(<<===>=>)的简单查询。对于数组字段,您可以使用单字段索引执行 array_contains 查询。

为了详细地予以说明,我们将从创建索引的角度来看看 cities 示例。下面的示例在 cities 集合中创建了一些 city 文档并为每个文档设置了 namestatecountrycapitalpopulationtags 字段:

网页
var citiesRef = db.collection("cities");

citiesRef.doc("SF").set({
    name: "San Francisco", state: "CA", country: "USA",
    capital: false, population: 860000,
    regions: ["west_coast", "norcal"] });
citiesRef.doc("LA").set({
    name: "Los Angeles", state: "CA", country: "USA",
    capital: false, population: 3900000,
    regions: ["west_coast", "socal"] });
citiesRef.doc("DC").set({
    name: "Washington, D.C.", state: null, country: "USA",
    capital: true, population: 680000,
    regions: ["east_coast"] });
citiesRef.doc("TOK").set({
    name: "Tokyo", state: null, country: "Japan",
    capital: true, population: 9000000,
    regions: ["kanto", "honshu"] });
citiesRef.doc("BJ").set({
    name: "Beijing", state: null, country: "China",
    capital: true, population: 21500000,
    regions: ["jingjinji", "hebei"] });

对于每项 set 操作,Cloud Firestore 会为每个非数组字段更新两个单字段索引(一个以升序排序的索引和一个以降序排序的索引),并为数组字段更新一个“数组包含”单字段索引。

Cloud Firestore 会为数组字段更新一个“数组包含”单字段索引,并为每个非数组字段更新两个单字段索引。下表中的每一行代表单字段索引中的一个条目:

集合 编入索引的字段
cities arrow_upward name
cities arrow_upward state
cities arrow_upward country
cities arrow_upward capital
cities arrow_upward population
cities arrow_downward name
cities arrow_downward state
cities arrow_downward country
cities arrow_downward capital
cities arrow_downward population
cities array-contains regions

您可以使用这些自动创建的索引运行简单的查询,如下所示:

网页
citiesRef.where("state", "==", "CA")
citiesRef.where("population", "<", 100000)
citiesRef.where("name", ">=", "San Francisco")

您可以使用 array_contains 索引查询 regions 数组字段:

网页
citiesRef.where("regions", "array-contains", "west_coast")

您还可以创建基于等式 (==) 的复合查询:

网页
citiesRef.where("state", "==", "CO").where("name", "==", "Denver")
citiesRef.where("country", "==", "USA").where("capital", "==", false).where("state", "==", "CA").where("population", "==", 860000)

如果您需要运行使用了范围比较运算符(<<=>>=)的复合查询,或者需要按照不同的字段进行排序,则必须为该查询创建一个复合索引

复合索引

复合索引会为一个集合中包含多个特定字段(而不是一个字段)的所有文档存储一个映射,并按顺序排序。复合索引还会为每个字段定义索引模式(升序、降序或“数组包含”),索引将会根据这些索引模式排序。

Cloud Firestore 使用复合索引执行单字段索引不支持的复合查询。例如,您需要使用复合索引执行下列查询:

网页
citiesRef.where("country", "==", "USA").orderBy("population", "asc")
citiesRef.where("country", "==", "USA").where("population", "<", 3800000)
citiesRef.where("country", "==", "USA").where("population", ">", 690000)

这些查询需要使用下列复合索引。请注意,由于该查询为 country 字段使用了等式,因此该字段的索引模式可以为降序,也可以为升序。默认情况下,根据不等式子句中所使用的字段,不等式查询会使用升序排序。

集合 编入索引的字段
cities arrow_upward(或 arrow_downward)country、arrow_upward population

不同于单字段索引,Cloud Firestore 不会自动创建复合索引,因为可能的字段组合数太多。但是,Cloud Firestore 可以帮您在构建应用时识别并创建必需的复合索引

如果您在没有创建必需的索引之前尝试执行上述查询,Cloud Firestore 会返回一个包含链接的错误消息,按照链接中的说明操作即可创建缺少的索引。如果您尝试运行不被索引支持的查询,就会出现这种情况。您还可以使用控制台或 Firebase CLI 手动定义和管理复合索引。如需详细了解如何创建并管理索引,请参阅管理索引

如果想要以降序运行相同的查询,您需要为 population 额外创建一个降序的复合索引:

网页
citiesRef.where("country", "==", "USA").orderBy("population", "desc")

citiesRef.where("country", "==", "USA")
         .where("population", "<", 3800000)
         .orderBy("population", "desc")

citiesRef.where("country", "==", "USA")
         .where("population", ">", 690000)
         .orderBy("population", "desc")
集合 编入索引的字段
cities arrow_upward country、arrow_upward population
cities arrow_upward countryarrow_downward population

如果您要将 array_contains 查询与其他子句组合使用,则还需要创建复合索引。

网页
citiesRef.where("regions", "array_contains", "east_coast")
         .where("capital", "==", true)
集合 编入索引的字段
cities array-contains tags、arrow_upward(或 arrow_downward)capital

索引和价格

如果使用索引,您的应用会产生存储费用。如需详细了解如何计算索引的存储空间使用量,请参阅索引条目大小

充分利用索引合并功能

虽然 Cloud Firestore 会为每个查询都使用索引,但实际上没有必要为每个查询都创建一个索引。如果查询使用多个等式 (==) 子句和非必需的 orderBy 子句,Cloud Firestore 可以重复利用现有的索引。Cloud Firestore 可以将针对简单等式过滤条件创建的索引合并起来,构建运行更大的等式查询所需的复合索引。

通过找出可受益于索引合并的情况,您可以降低索引产生的费用。例如,假设一个餐厅评分应用有一个 restaurants 集合:

  • collections_bookmark restaurants

    • class burgerthyme

      name : "Burger Thyme"
      category : "burgers"
      city : "San Francisco"
      editors_pick : true
      star_rating : 4

现在,假设该应用使用下列查询。请注意,该应用使用的是 categorycityeditors_pick 等式子句的组合,并始终以降序方式为 star_rating 排序:

网页
db.collection("restaurants").where("category", "==", "burgers")
                            .orderBy("star_rating")

db.collection("restaurants").where("city", "==", "San Francisco")
                            .orderBy("star_rating")

db.collection("restaurants").where("category", "==", "burgers")
                            .where("city", "==", "San Francisco")
                            .orderBy("star_rating")

db.collection("restaurants").where("category", "==", "burgers")
                            .where("city", "==" "San Francisco")
                            .where("editors_pick", "==", true )
                            .orderBy("star_rating")

您可以为每个查询创建一个索引:

集合 编入索引的字段
restaurants arrow_upward category、arrow_upward star_rating
restaurants arrow_upward city、arrow_upward star_rating
restaurants arrow_upward category、arrow_upward city、arrow_upward star_rating
restaurants arrow_upward category、arrow_upward city、arrow_upward editors_pick、arrow_upward star_rating

更好的方式是,充分利用 Cloud Firestore 为等式子句合并索引的功能,减少索引的数量:

集合 编入索引的字段
restaurants arrow_upward category、arrow_upward star_rating
restaurants arrow_upward city、arrow_upward star_rating
restaurants arrow_upward editors_pick、arrow_upward star_rating

此索引集不仅更小,还支持执行额外的查询:

网页
db.collection("restaurants").where("editors_pick", "==", true)
                            .orderBy("star_rating")

索引限制

索引存在以下限制。如需了解与配额和限制有关的全部信息,请参阅配额和限制

限制 详细信息
一个数据库的复合索引数量上限 200
一个数据库的单字段索引例外项的数量上限 200

每个文档的索引条目数量上限

40000

索引条目的数量是文档的以下各项数量的总和:

  • 单字段索引条目的数量
  • 复合索引条目的数量
索引条目的大小上限

7.5 KiB

要了解 Cloud Firestore 如何计算索引条目大小,请参阅索引条目大小

一个文档的索引条目的大小总和上限

8 MiB

总大小是文档的以下各项的大小总和:

  • 一个文档的单字段索引条目的大小总和
  • 一个文档的复合索引条目的大小总和
  • 编入索引的字段值的大小上限

    1500 字节

    超出 1500 字节的字段值会被截断。包含被截断的字段值的查询可能会返回不一致的结果。

    索引最佳做法

    对于大多数应用,您可以依靠自动索引和错误消息链接来管理索引。但是,在以下情况下,您可能需要添加单字段例外项:

    场景 说明
    大型字符串字段

    如果您的字符串字段通常包含不用于查询的长字符串值,您可以选择不将该字段编入索引来降低存储费用。

    向包含具有依序值的文档的集合高速写入数据

    如果您将在某个集合中的各文档之间依序递增或递减的字段(如时间戳)编入索引,则向该集合写入数据的最大速率为每秒 500 次写入。如果您不根据具有依序值的字段进行查询,则可以选择不将该字段编入索引来绕过此限制。

    例如,在具有高写入速率的 IoT 使用场景中,一个包含具有时间戳字段的文档的集合可能会达到每秒 500 次写入的限制。

    大型数组或映射字段

    大型数组或映射字段可能会达到每个文档 20000 个索引条目的限制。如果您没有根据大型数组或映射字段进行查询,则应该选择不将该字段编入索引。

    发送以下问题的反馈:

    此网页
    需要帮助?请访问我们的支持页面