索引类型

索引是决定数据库性能的一个重要因素。就像书的索引可以将书中的主题映射到页码一样,数据库中的索引可以将数据库中的数据项映射到数据库中的位置。当您查询数据库时,数据库可以使用索引来快速确定您请求的项目的位置。

本页面介绍了 Cloud Firestore 使用的两种索引类型:自动索引和手动索引。

索引定义和结构

索引是在给定文档的字段列表上定义的,每个字段都有相应的索引模式。

对于索引定义中指定的每个字段,索引都会包含一个相应的条目。索引包含根据索引进行查询时,可能会出现在结果中的所有文档。只有当针对索引中使用的每个字段,文档都设置有索引值时,该文档才会包含在索引中。如果文档没有为索引定义所引用的字段设置值,该文档便不会出现在索引中。在这种情况下,基于索引的所有查询都不会将该文档作为结果返回。

索引按字段值以索引定义中指定的顺序进行排序。

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

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

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

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

索引类型

Cloud Firestore 使用两种索引类型:自动索引和手动索引。手动索引和自动索引在管理方式上有所不同。

自动索引

默认情况下,Cloud Firestore 会自动为集合的文档中的每个字段构建索引。借助这些单字段索引,您可以执行许多基本查询。您可以通过配置数据库的自动索引设置和索引例外项来管理自动索引。

自动索引默认值

Cloud Firestore 对自动索引使用以下默认设置:

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

  • 每个非数组、非映射子字段都有一个集合范围的升序索引。

  • 每个非数组、非映射子字段都有一个集合范围的降序索引。
  • 一个针对整个映射值的集合范围升序索引
  • 一个针对整个映射值的集合范围降序索引
  • 每个数组子字段都有一个集合范围的 array-contains 索引。
  • Cloud Firestore 会递归地为每个映射子字段编制索引。
  • 对于文档中的每个数组字段,Cloud Firestore 会创建以下索引:

  • 一个针对整个数组值的集合范围升序索引

  • 一个针对整个数组值的集合范围降序索引
  • 一个集合范围的 array-contains 索引。
  • 默认情况下,系统不维护具有集合组范围的自动索引。

自动索引例外项

您可以通过创建索引例外项来从自动索引设置中排除字段。索引例外项会覆盖适用于整个数据库的自动索引设置。例外项可让您启用自动索引设置停用的索引,或停用自动索引启用的索引。有关例外项的适用情况,请参阅索引最佳做法。

使用 * 字段路径值,为集合组中的所有字段添加集合级索引例外项。例如,对于集合组 comments,将字段路径设置为 *,使之匹配 comments 集合组中的所有字段,并禁止将该集合组下的所有字段编入索引。然后,可以添加例外项,以便仅将查询所需的字段编入索引。如果减少编入索引的字段的数量,则可以降低存储费用并提高写入性能。

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

如需创建和管理自动索引例外项,请参阅管理索引。

手动索引

手动索引根据要编入索引的字段的有序列表为一个集合中的所有文档存储一个映射,并按顺序对其排序。

Cloud Firestore 使用手动索引来支持自动索引尚不支持的查询。

默认情况下,Cloud Firestore 会自动为集合中的每个字段创建单字段索引。Cloud Firestore 不会自动为字段组合创建索引,因为可能的字段组合数太多。但是,Cloud Firestore 可以帮助您在构建应用时识别并创建必需的索引。

每当您尝试运行现有索引不支持的查询时,Cloud Firestore 都会返回一条错误消息,并提供一个链接,您可以点击该链接来创建缺少的索引。

您还可以使用控制台或 Firebase CLI 手动定义和管理索引。如需详细了解如何创建和管理手动索引,请参阅管理索引。

索引模式和查询范围

虽然自动索引和手动索引的配置方式不同,但都需要为其配置索引模式和查询范围。

索引模式

定义索引时,需要为每个编入索引的字段选择索引模式。每个字段的索引模式都支持针对该字段的特定查询子句。您可以选择以下某种索引模式:

索引模式 说明
升序 支持针对该字段的 <、<=、==、>=、>、!=、in 和 not-in 查询子句,并且支持根据此字段值按升序对结果进行排序。
降序 支持针对该字段的 <、<=、==、>=、>、!=、in 和 not-in 查询子句,并且支持根据此字段值按降序对结果进行排序。
Array‑contains 支持针对该字段的 array-contains 和 array-contains-any 查询子句。
矢量 支持在该字段上使用 FindNearest 查询子句。

查询范围

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

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

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

默认排序方式和 __name__ 字段

除了按每个字段的指定索引模式(升序或降序)对文档进行排序之外,索引还会按每个文档的 __name__ 字段进行最终排序。__name__ 字段的值设置为完整的文档路径。这意味着结果集中具有相同字段值的文档将按文档路径排序。

默认情况下,__name__ 字段的排序方向与索引定义中最后排序的字段的方向相同。例如:

集合 编入索引的字段 查询范围
cities name, __name__ 集合
cities state, __name__ 集合
cities country, population, __name__ 集合

如需按非默认 __name__ 方向对结果排序,您需要创建该索引。

索引属性

可让查询以最高效方式执行的索引由以下属性定义:

  • 等式过滤条件中使用的字段
  • 排序顺序中使用的字段
  • 范围和不等式过滤条件中使用的字段(尚未包含在排序顺序中)
  • 聚合中使用的字段(尚未包含在排序顺序及范围和不等式过滤条件中)

Cloud Firestore 按如下方式计算查询结果:

  1. 找出与查询的集合、过滤条件属性、过滤条件运算符和排序顺序相对应的索引。
  2. 确定扫描开始的索引位置。起始位置以查询的等式过滤条件作为前缀,以第一个 orderBy 字段上的范围和不等式过滤条件结尾。
  3. 开始扫描索引,返回满足所有过滤条件的每个文档,直到扫描过程满足以下条件之一:
  4. 发现不满足过滤条件的文档,并确认后续的所有文档都不能完全满足过滤条件。
  5. 到达索引的末尾。
  6. 收集查询所请求的最大结果数量。

索引示例

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

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

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

test.firestore.js

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

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

单字段索引支持的查询

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

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

test.firestore.js

您还可以创建 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"])

手动索引支持的查询

创建手动索引以支持自动单字段索引尚不支持的复合查询。 例如,您需要使用手动索引来支持以下查询:

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-contains 或 array-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 集合

集合组索引支持的查询

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

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()

索引条目

项目的已配置索引和文档的结构决定了文档索引条目的数量。这些索引条目均会计入索引条目数限制。

以下示例展示了文档的索引条目。

文档

/cities/SF

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

自动索引

  • city_name ASC
  • city_name DESC
  • neighborhoods ASC
  • neighborhoods DESC
  • temperatures ASC
  • temperatures DESC
  • temperatures.summer ASC
  • temperatures.summer DESC
  • temperatures.winter ASC
  • temperatures.winter DESC
  • neighborhoods 数组包含

手动索引

  • city_name ASC, neighborhoods ARRAY
  • city_name DESC, neighborhoods ARRAY