使用 Firestore 流水线操作修改数据

使用 update(...) 和 delete(...) 数据操纵语言 (DML) 阶段构建可查询文档然后删除或修改数据的数据流水线。

版本要求

本页介绍的操作需要 Firestore 企业版。

准备工作

您应该熟悉如何使用流水线操作查询数据库。

更新文档

使用 update(...) DML 阶段构建可查询文档并添加或修改数据的数据流水线。

所有 DML 阶段都必须位于流水线的末尾。 进入此阶段的文档必须包含 __name__ 字段,以标识要更新的文档。如果您尝试更新的任何文档不存在,则操作会失败。

例如,以下操作会将数据模型更改回填到集合组中的所有文档。流水线会向 users 集合组中缺少 preferences.color 字段的所有文档添加该字段。

Node.js
const snapshot = await db.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant(null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Constant, Field, Not

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Not(Field.of("preferences.color").exists()))
    .add_fields(Constant.of(None).as_("preferences.color"))
    .remove_fields("color")
    .update()
    .execute()
)
Java
Pipeline.Snapshot snapshot = firestore.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant((String) null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute().get();
Go
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.Not(firestore.FieldExists(firestore.FieldOf("preferences.color")))).
	AddFields(firestore.Selectables(
		firestore.ConstantOfNull().As("preferences.color"),
	)).
	RemoveFields(firestore.Fields("color")).
	Update().
	Execute(ctx)

删除文档

使用 delete(...) DML 阶段构建可查询文档然后删除数据的数据流水线。为防止意外批量删除,以 delete(...) 结尾的流水线应至少包含一个 where(...) 阶段。所有 DML 阶段都必须位于流水线的末尾。

例如,以下流水线会删除 address.users 设置为 USA 且 __create_time__ 少于 10 天的所有 users 文档:

Node.js
const pipeline = db.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").timestampAdd("day", 10).lessThan(currentTimestamp()))
  .delete();
await pipeline.execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import CurrentTimestamp, Field

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Field.of("address.country").equal("USA"))
    .where(
        Field.of("__create_time__")
        .timestamp_add("day", 10)
        .less_than(CurrentTimestamp())
    )
    .delete()
    .execute()
)
Java
Pipeline.Snapshot deleteResults = firestore.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").add(constant(10)).lessThan(currentTimestamp()))
  .delete()
  .execute().get();
Go
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.FieldOf("address.country").Equal("USA")).
	Where(firestore.FieldOf("__create_time__").Add(firestore.ConstantOf(10)).LessThan(firestore.CurrentTimestamp())).
	Delete().
	Execute(ctx)

执行模式和一致性

DML 阶段可以在显式事务内执行,也可以在事务外执行:

事务性执行

事务性 DML 可在文档之间提供原子性、全有或全无的一致性,并支持全套流水线查询阶段。这使其非常适合需要严格一致性保证(不接受部分更新)或依赖复杂查询阶段的工作负载,前提是修改符合事务大小限制。

在显式事务中运行 DML 流水线(使用 runTransaction / transaction.execute(pipeline) 或 atomic: true 选项)具有以下行为:

  • 所有变更要么一起提交,要么在失败时完全回滚。
  • DML 阶段之前的所有流水线查询阶段均受支持(包括 where、add_fields、union、find_nearest 和 aggregate 子查询)。
  • 在事务中,所有读取操作都必须在任何写入操作之前执行。由于 DML 流水线会执行写入操作,因此您无法在同一事务中运行 DML 流水线后执行读取操作。例如,您不能在单个事务中多次调用 DML 流水线,也不能在执行 DML 流水线后读取文档。在 DML 流水线运行后尝试执行读取操作失败,并显示 FAILED_PRECONDITION 错误:Cannot have reads after the first write in a Firestore transaction.
  • 该操作受 Firestore 企业版事务限制的约束。

非事务性执行

非事务性 DML 专为跨大量文档进行批量修改而设计,不会遇到事务大小限制。通过在文档级(而非整个操作)锁定,它允许其他并发操作在批量更新文档时继续进行。

当 DML 阶段在显式事务之外运行时:

  • 独立更新和部分成功:每个文档都是独立更新的,并且操作在文档之间不具有原子性。流水线会在出现第一个错误时停止,并且之前修改的文档仍会写入数据库,这可能会导致部分成功。
  • 阶段支持:
    • 在事务之外的 DML 之前,不支持高级和多查询阶段(例如 aggregate(...)、distinct(...)、unnest(...)、find_nearest(...)、union(...)、联接和子查询)。如果您的流水线需要这些阶段,请使用事务。
  • 安全规则 20 次查找限制:每批文档都会在自己的事务中执行,并重新评估安全规则。如果您的安全规则使用 get() 或 exists() 查找,则每个批次都会执行一次查找,在写入大约 200 个文档后,就会达到每个请求 20 次查找的上限,并失败并显示 PERMISSION_DENIED。请使用自定义声明(而非 get() 或 exists() 查找)来避免此限制。

限制

请注意 DML 阶段的以下限制:

  • 在调用 .execute() 之前,DML 阶段必须是流水线定义中的最后一个阶段。
  • 在事务之外运行时,每个文档都会独立于其他文档进行更新,并且允许部分成功。
  • 由于存在 20 次查找限制,受具有 get() 或 exists() 查找的安全规则约束的非事务性 DML 操作最多只能处理大约 200 个文档。
  • 事务性 DML 操作受 Firestore 企业版事务限制的约束。由于 Firestore 事务不允许在首次写入后进行读取,因此您无法在单个事务中多次调用 DML 流水线,也无法在执行 DML 流水线后读取文档。在 DML 写入后尝试执行读取操作会失败,并显示 FAILED_PRECONDITION 错误:Cannot have reads after the first write in a Firestore transaction.
  • 如果 DML 阶段之前的阶段生成了多个具有相同 __name__ 的文档,或者计算结果为相同的目标文档 ID,则每个变更都会按顺序处理:
    • 对于 upsert(...) 和 update(...):针对同一文档的多个变更遵循“最后一次变更生效”语义,即最后处理的变更决定文档的最终状态。
    • 对于 delete(...):第一次尝试会删除文档,而对同一文档的后续尝试则不执行任何操作。
    • 对于 insert(...):第一次尝试会创建文档,而后续针对同一文档 ID 的尝试会失败,并显示 ALREADY_EXISTS 错误。