Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

Cloud Firestore 中的索引类型

索引是决定数据库性能的一个重要因素。就像书的索引可以将章节标题映射到页码一样,数据库的索引可以将数据库的内容映射到具体的位置。当您向数据库发送查询请求时,数据库可以使用索引来快速查找所需内容的位置。

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

每个查询的背后都有索引在发挥作用

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

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

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

索引类型

Cloud Firestore 使用了两种索引类型:单字段索引和复合索引。除了编入索引的字段数量不同之外,单字段索引和复合索引的管理方式也不同。

单字段索引

单字段索引会为一个集合中包含某个特定字段的所有文档存储一个映射,并按顺序排序。单字段索引中的每个条目会记录文档中一个特定字段的值,以及该文档在数据库中的位置。Cloud Firestore 使用这些索引执行许多基本查询。您可以通过配置数据库的自动索引设置和索引例外项来管理单字段索引。

自动编入索引

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

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

  • 对于每个映射字段,Cloud Firestore 会为映射中的每个既非数组也非映射的子字段创建一个集合范围的升序索引和一个降序索引。

  • 对于文档中的每个数组字段,Cloud Firestore 会创建并维护一个集合范围的 array-contains 索引。

  • 默认情况下系统不维护采用集合组范围的单字段索引。

单字段索引例外项

您可以通过创建单字段索引例外项来从自动索引设置中排除字段。 索引例外项会覆盖针对整个数据库的自动索引设置。例外项可让您启用自动索引设置会停用的单字段索引,或停用自动索引设置会启用的单字段索引。如需了解例外项的适用场景,请参阅索引最佳做法

如果为映射字段创建单字段索引例外项,映射的子字段会继承这些设置。不过,您也可以为特定子字段定义单字段索引例外项。如果删除子字段的例外项,子字段将继承其父级的例外项设置(如果有),如果没有父级例外项,则继承数据库范围的设置。

如需了解如何创建和管理单字段索引例外项,请参阅在 Cloud Firestore 中管理索引

复合索引

复合索引根据要建立索引的字段的有序列表为一个集合中的所有文档存储一个映射,并按顺序排序。

Cloud Firestore 使用复合索引来支持单字段索引不支持的查询。

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

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

索引模式和查询范围

虽然单字段索引和复合索引的配置方式不同,但都需要为索引配置索引模式和查询范围。

索引模式

定义索引时,您可以为每个已编入索引的字段选择索引模式。每个字段的索引模式都支持针对该字段的特定查询子句。您可以从以下索引模式中进行选择:

索引模式 说明
升序 支持针对该字段的 <<===>=>in 查询子句,并且支持根据此字段值按升序对结果进行排序。
降序 支持针对该字段的 <<===>=>in 查询子句,并且支持根据此字段值按降序对结果进行排序。
Array‑contains 支持针对该字段的 array-containsarray-contains-any 查询子句。

查询范围

每个索引的范围都限定为一个集合或集合组。这就是索引的查询范围:

集合范围
默认情况下,Cloud Firestore 使用集合范围创建索引。 这些索引支持从单个集合返回结果的查询。

集合组范围
集合组包括具有相同集合 ID 的所有集合。要运行从集合组返回已过滤或已排序结果的集合组查询,必须使用集合组范围创建相应的索引。

索引示例

Cloud Firestore 自动为您创建单字段索引,让您的应用可以快速执行最基本的数据库查询。您可以使用单字段索引执行基于字段值和比较运算符(<<===>=>in)的简单查询。对于数组字段,您可以使用单字段索引执行 array-containsarray-contains-any 查询。

为便于说明,请从索引创建的角度查看以下示例。以下代码段在 cities 集合中创建了一些 city 文档,并为每个文档设置了 namestatecountrycapitalpopulationtags 字段:

Web
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"] });

假设使用默认的自动索引设置,Cloud Firestore 会为每个非数组字段更新一个升序单字段索引和一个降序单字段索引,并为数组字段更新一个 array-contains 单字段索引。下表中的每一行代表单字段索引中的一个条目:

集合 编入索引的字段 查询范围
cities name 集合
cities state 集合
cities country 集合
cities capital 集合
cities population 集合
cities name 集合
cities state 集合
cities country 集合
cities capital 集合
cities population 集合
cities array-contains regions 集合

单字段索引支持的查询

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

Web
const stateQuery = citiesRef.where("state", "==", "CA");
const populationQuery = citiesRef.where("population", "<", 100000);
const nameQuery = citiesRef.where("name", ">=", "San Francisco");

您还可以创建 in 和复合等式 (==) 查询:

Web
citiesRef.where('country', 'in', ["USA", "Japan", "China"])

// Compound equality queries
citiesRef.where("state", "==", "CO").where("name", "==", "Denver")
citiesRef.where("country", "==", "USA")
         .where("capital", "==", false)
         .where("state", "==", "CA")
         .where("population", "==", 860000)

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

您可以使用 array-contains 索引查询 regions 数组字段:

Web
citiesRef.where("regions", "array-contains", "west_coast")
// array-contains-any and array-contains use the same indexes
citiesRef.where("regions", "array-contains-any", ["west_coast", "east_coast"])

复合索引支持的查询

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

Web
citiesRef.where("country", "==", "USA").orderBy("population", "asc")
citiesRef.where("country", "==", "USA").where("population", "<", 3800000)
citiesRef.where("country", "==", "USA").where("population", ">", 690000)
// in and == clauses use the same index
citiesRef.where("country", "in", ["USA", "Japan", "China"])
         .where("population", ">", 690000)

这些查询需要使用下列复合索引。由于查询针对 country 字段使用等式(==in),因此您可以对此字段使用升序或降序索引模式。默认情况下,不等式子句会基于不等式子句中的字段使用升序排序。

集合 编入索引的字段 查询范围
cities (或 )country、 population 集合

要以降序运行相同的查询,您需要针对 population 字段额外创建一个降序的复合索引:

Web
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")

citiesRef.where("country", "in", ["USA", "Japan", "China"])
         .where("population", ">", 690000)
         .orderBy("population", "desc")
集合 编入索引的字段 查询范围
cities country、 population 集合
cities country population 集合

如果您要将 array-containsarray-contains-any 查询与其他子句组合使用,则也需要创建复合索引。

Web
citiesRef.where("regions", "array-contains", "east_coast")
         .where("capital", "==", true)

// array-contains-any and array-contains use the same index
citiesRef.where("regions", "array-contains-any", ["west_coast", "east_coast"])
         .where("capital", "==", true)
集合 编入索引的字段 查询范围
cities array-contains tags、(或 )capital 集合

集合组索引支持的查询

为了演示范围为集合组的索引,假设您将 city 子集合添加到部分 landmarks 文档:

Web
var citiesRef = db.collection("cities");

citiesRef.doc("SF").collection("landmarks").doc().set({
    name: "Golden Gate Bridge",
    category : "bridge" });
citiesRef.doc("SF").collection("landmarks").doc().set({
    name: "Golden Gate Park",
    category : "park" });

citiesRef.doc("DC").collection("landmarks").doc().set({
    name: "National Gallery of Art",
    category : "museum" });
citiesRef.doc("DC").collection("landmarks").doc().set({
    name: "National Mall",
    category : "park" });

使用集合范围的以下单字段索引,您可以根据 category 字段查询单个城市的 landmarks 集合:

集合 编入索引的字段 查询范围
landmarks (或 )category 集合
Web
citiesRef.doc("SF").collection("landmarks").where("category", "==", "park")
citiesRef.doc("SF").collection("landmarks").where("category", "in", ["park", "museum"])

现在,假设您想要查询所有城市的地标。为了针对包含所有 landmarks 集合的集合组运行此查询,必须启用具有集合组范围的 landmarks 单字段索引:

集合 编入索引的字段 查询范围
landmarks (或 )category 集合组

启用此索引后,您可以查询 landmarks 集合组:

Web
var landmarksGroupRef = db.collectionGroup("landmarks");

landmarksGroupRef.where("category", "==", "park")
landmarksGroupRef.where("category", "in", ["park", "museum"])

若要运行返回已过滤或已排序结果的集合组查询,必须启用范围为集合组的相应单字段索引或复合索引。但是,不过滤或不排序结果的集合组查询不需要任何其他索引定义。

例如,您可以在不启用其他索引的情况下运行以下集合组查询:

Web
db.collectionGroup("landmarks").get()

索引条目

项目的已配置索引以及文档的结构都会影响文档的索引条目,这些条目最终计入索引条目数限制

下面举例说明。

文档

name : "San Francisco"
temperatures : {summer: 67, winter: 55}
neighborhoods : ["Mission", "Downtown", "Marina"]

单字段索引

  • (自动)name 升序和降序
  • (自动)temperatures 升序和降序
  • (自动)neighborhoods 数组包含

复合索引

  • name 升序,neighborhoods 升序
  • name 降序,neighborhoods 升序

结果索引条目

此索引配置会生成文档的下面 12 个索引条目:

索引 条目
name 升序和降序 name: "San Francisco"
temperatures 升序和降序 temperatures.summer: 67
temperatures 升序和降序 temperatures.winter: 55
neighborhoods 数组包含 neighborhoods: "Mission"
neighborhoods 数组包含 neighborhoods: "Downtown"
neighborhoods 数组包含 neighborhoods: "Marina"
name 升序,neighborhoods 升序 name: "San Francisco", neighborhoods: "Mission"
name 升序,neighborhoods 升序 name: "San Francisco", neighborhoods: "Downtown"
name 升序,neighborhoods 升序 name: "San Francisco", neighborhoods: "Marina"
name 降序,neighborhoods 升序 name: "San Francisco", neighborhoods: "Mission"
name 降序,neighborhoods 升序 name: "San Francisco", neighborhoods: "Downtown"
name 降序,neighborhoods 升序 name: "San Francisco", neighborhoods: "Marina"

索引和价格

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

充分利用索引合并功能

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

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

  • restaurants

    • burgerthyme

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

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

Web
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 category、 star_rating 集合
restaurants city、 star_rating 集合
restaurants category、 city、 star_rating 集合
restaurants category、 city、 editors_pick、 star_rating 集合

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

集合 编入索引的字段 查询范围
restaurants category、 star_rating 集合
restaurants city、 star_rating 集合
restaurants editors_pick、 star_rating 集合

此索引集不仅更小,还可支持其他查询:

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

索引限制

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

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

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

40000

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

  • 单字段索引条目的数量
  • 复合索引条目的数量

如需了解 Cloud Firestore 如何将一个文档和一组索引转变为索引条目,请参阅此索引条目数示例

索引条目的大小上限

7.5 KiB

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

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

8 MiB

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

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

    1500 字节

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

    索引最佳做法

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

    场景 说明
    大型字符串字段

    如果您的数据库包含一个通常具有长字符串值并且不用于查询的字符串字段,可以选择不将其编入索引来降低存储费用。

    向所含文档具有依序值的集合进行高速率写入

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

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

    大型数组或映射字段

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