事务和批量写入

Firestore 支持通过原子操作读取和写入数据。在一组原子操作中,要么所有操作都执行成功,要么一个都不执行。Firestore 中有两种类型的原子操作:

  • 事务事务是对一个或多个文档进行的一组读写操作。
  • 批量写入批量写入是对一个或多个文档进行的一组写入操作。

利用事务更新数据

使用 Firestore 客户端库,您可以将多个操作划分到单个事务中。如果您想根据某个字段当前的值或其他字段的值来更新这个字段的值,则事务会很有用。

一个事务可包含任意数量的 get() 操作,后跟任意数量的写入操作(例如 set()update()delete())。在出现并发修改的情况下,Firestore 会再次运行整个事务。例如,如果某个事务读取若干文档,而另一个客户端要修改其中任何一个文档,则 Firestore 会重试该事务。此功能可确保事务在最新且一致的数据上运行。

事务绝对不会只执行部分写入操作。所有写入操作都是在事务成功结束时才执行的。

使用事务时请注意:

  • 读取操作必须在写入操作之前执行。
  • 如果某个修改操作会影响某项事务读取的文档,则同时并发的调用该事务的函数(事务函数)可能会运行多次。
  • 事务函数不应该直接修改应用状态。
  • 当客户端处于离线状态时,事务将失败。

以下示例展示了如何创建和运行事务:

Web 版本 9

import { runTransaction } from "firebase/firestore";

try {
  await runTransaction(db, async (transaction) => {
    const sfDoc = await transaction.get(sfDocRef);
    if (!sfDoc.exists()) {
      throw "Document does not exist!";
    }

    const newPopulation = sfDoc.data().population + 1;
    transaction.update(sfDocRef, { population: newPopulation });
  });
  console.log("Transaction successfully committed!");
} catch (e) {
  console.log("Transaction failed: ", e);
}

Web 版本 8

// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

// Uncomment to initialize the doc.
// sfDocRef.set({ population: 0 });

return db.runTransaction((transaction) => {
    // This code may get re-run multiple times if there are conflicts.
    return transaction.get(sfDocRef).then((sfDoc) => {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        // Add one person to the city population.
        // Note: this could be done without a transaction
        //       by updating the population using FieldValue.increment()
        var newPopulation = sfDoc.data().population + 1;
        transaction.update(sfDocRef, { population: newPopulation });
    });
}).then(() => {
    console.log("Transaction successfully committed!");
}).catch((error) => {
    console.log("Transaction failed: ", error);
});
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
let sfReference = db.collection("cities").document("SF")

do {
  let _ = try await db.runTransaction({ (transaction, errorPointer) -> Any? in
    let sfDocument: DocumentSnapshot
    do {
      try sfDocument = transaction.getDocument(sfReference)
    } catch let fetchError as NSError {
      errorPointer?.pointee = fetchError
      return nil
    }

    guard let oldPopulation = sfDocument.data()?["population"] as? Int else {
      let error = NSError(
        domain: "AppErrorDomain",
        code: -1,
        userInfo: [
          NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)"
        ]
      )