转到控制台

事务和批量写入

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

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

每个事务或批量写入最多可以向 500 个文档写入数据。要了解与写入相关的其他限制,请参阅配额和限制

利用事务更新数据

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

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

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

使用事务时请注意:

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

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

Web
// 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(function(transaction) {
    // This code may get re-run multiple times if there are conflicts.
    return transaction.get(sfDocRef).then(function(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(function() {
    console.log("Transaction successfully committed!");
}).catch(function(error) {
    console.log("Transaction failed: ", error);
});
Swift
let sfReference = db.collection("cities").document("SF")

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)"
            ]
        )
        errorPointer?.pointee = error
        return nil
    }

    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference)
    return nil
}) { (object, error) in
    if let error = error {
        print("Transaction failed: \(error)")
    } else {
        print("Transaction successfully committed!")
    }
}
Objective-C
FIRDocumentReference *sfReference =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) {
  FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer];
  if (*errorPointer != nil) { return nil; }

  if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{
      NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot"
    }];
    return nil;
  }
  NSInteger oldPopulation = [sfDocument.data[@"population"] integerValue];

  // Note: this could be done without a transaction
  //       by updating the population using FieldValue.increment()
  [transaction updateData:@{ @"population": @(oldPopulation + 1) } forDocument:sfReference];

  return nil;
} completion:^(id result, NSError *error) {
  if (error != nil) {
    NSLog(@"Transaction failed: %@", error);
  } else {
    NSLog(@"Transaction successfully committed!");
  }
}];
  
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF");

db.runTransaction(new Transaction.Function<Void>() {
    @Override
    public Void apply(Transaction transaction) throws FirebaseFirestoreException {
        DocumentSnapshot snapshot = transaction.get(sfDocRef);

        // Note: this could be done without a transaction
        //       by updating the population using FieldValue.increment()
        double newPopulation = snapshot.getDouble("population") + 1;
        transaction.update(sfDocRef, "population", newPopulation);

        // Success
        return null;
    }
}).addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        Log.d(TAG, "Transaction success!");
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.w(TAG, "Transaction failure.", e);
    }
});
Kotlin
val sfDocRef = db.collection("cities").document("SF")

db.runTransaction { transaction ->
    val snapshot = transaction.get(sfDocRef)

    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    val newPopulation = snapshot.getDouble("population")!! + 1
    transaction.update(sfDocRef, "population", newPopulation)

    // Success
    null
}.addOnSuccessListener { Log.d(TAG, "Transaction success!") }
        .addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
// Initialize doc
final DocumentReference docRef = db.collection("cities").document("SF");
City city = new City("SF");
city.setCountry("USA");
city.setPopulation(860000L);
docRef.set(city).get();

// run an asynchronous transaction
ApiFuture<Void> transaction =
    db.runTransaction(
        new Transaction.Function<Void>() {
          @Override
          public Void updateCallback(Transaction transaction) throws Exception {
            // retrieve document and increment population field
            DocumentSnapshot snapshot = transaction.get(docRef).get();
            long oldPopulation = snapshot.getLong("population");
            transaction.update(docRef, "population", oldPopulation + 1);
            return null;
          }
        });
// block on transaction operation using transaction.get()
Python
transaction = db.transaction()
city_ref = db.collection(u'cities').document(u'SF')

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    transaction.update(city_ref, {
        u'population': snapshot.get(u'population') + 1
    })

update_in_transaction(transaction, city_ref)
Node.js
// Initialize document
let cityRef = db.collection('cities').doc('SF');
let setCity = cityRef.set({
  name: 'San Francisco',
  state: 'CA',
  country: 'USA',
  capital: false,
  population: 860000
});

let transaction = db.runTransaction(t => {
  return t.get(cityRef)
    .then(doc => {
      // Add one person to the city population.
      // Note: this could be done without a transaction
      //       by updating the population using FieldValue.increment()
      let newPopulation = doc.data().population + 1;
      t.update(cityRef, {population: newPopulation});
    });
}).then(result => {
  console.log('Transaction success!');
}).catch(err => {
  console.log('Transaction failure:', err);
});
Go
ref := client.Collection("cities").Doc("SF")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
	doc, err := tx.Get(ref) // tx.Get, NOT ref.Get!
	if err != nil {
		return err
	}
	pop, err := doc.DataAt("population")
	if err != nil {
		return err
	}
	return tx.Set(ref, map[string]interface{}{
		"population": pop.(int64) + 1,
	}, firestore.MergeAll)
})
if err != nil {
	// Handle any errors appropriately in this section.
	log.Printf("An error has occurred: %s", err)
}
PHP
$cityRef = $db->collection('cities')->document('SF');
$db->runTransaction(function (Transaction $transaction) use ($cityRef) {
    $snapshot = $transaction->snapshot($cityRef);
    $newPopulation = $snapshot['population'] + 1;
    $transaction->update($cityRef, [
        ['path' => 'population', 'value' => $newPopulation]
    ]);
});
C#
DocumentReference cityRef = db.Collection("cities").Document("SF");
await db.RunTransactionAsync(async transaction =>
{
    DocumentSnapshot snapshot = await transaction.GetSnapshotAsync(cityRef);
    long newPopulation = snapshot.GetValue<long>("Population") + 1;
    Dictionary<string, object> updates = new Dictionary<string, object>
    {
        { "Population", newPopulation}
    };
    transaction.Update(cityRef, updates);
});
Ruby
def run_simple_transaction project_id:
  # project_id = "Your Google Cloud Project ID"

  firestore = Google::Cloud::Firestore.new project_id: project_id

  city_ref = firestore.doc "cities/SF"

  firestore.transaction do |tx|
    new_population = tx.get(city_ref).data[:population] + 1
    puts "New population is #{new_population}."
    tx.update city_ref, population: new_population
  end
  puts "Ran a simple transaction to update the population field in the SF document in the cities collection."
end

将信息传递到事务之外

不要在您的事务函数中修改应用状态。这样做会引入并发问题,这是因为事务函数可能会运行多次,并且不能保证会在界面线程中运行。因此,您需要将自己需要的信息传递到事务函数之外。以下示例是基于上一个示例构建的,展示了如何将信息传递到事务之外:

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

db.runTransaction(function(transaction) {
    return transaction.get(sfDocRef).then(function(sfDoc) {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        var newPopulation = sfDoc.data().population + 1;
        if (newPopulation <= 1000000) {
            transaction.update(sfDocRef, { population: newPopulation });
            return newPopulation;
        } else {
            return Promise.reject("Sorry! Population is too big.");
        }
    });
}).then(function(newPopulation) {
    console.log("Population increased to ", newPopulation);
}).catch(function(err) {
    // This will be an "population is too big" error.
    console.error(err);
});
Swift
let sfReference = db.collection("cities").document("SF")

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)"
            ]
        )
        errorPointer?.pointee = error
        return nil
    }

    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    let newPopulation = oldPopulation + 1
    guard newPopulation <= 1000000 else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -2,
            userInfo: [NSLocalizedDescriptionKey: "Population \(newPopulation) too big"]
        )
        errorPointer?.pointee = error
        return nil
    }

    transaction.updateData(["population": newPopulation], forDocument: sfReference)
    return newPopulation
}) { (object, error) in
    if let error = error {
        print("Error updating population: \(error)")
    } else {
        print("Population increased to \(object!)")
    }
}
Objective-C
FIRDocumentReference *sfReference =
[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) {
  FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer];
  if (*errorPointer != nil) { return nil; }

  if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{
      NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot"
    }];
    return nil;
  }
  NSInteger population = [sfDocument.data[@"population"] integerValue];

  population++;
  if (population >= 1000000) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-2 userInfo:@{
      NSLocalizedDescriptionKey: @"Population too big"
    }];
    return @(population);
  }

  [transaction updateData:@{ @"population": @(population) } forDocument:sfReference];

  return nil;
} completion:^(id result, NSError *error) {
  if (error != nil) {
    NSLog(@"Transaction failed: %@", error);
  } else {
    NSLog(@"Population increased to %@", result);
  }
}];
  
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF");

db.runTransaction(new Transaction.Function<Double>() {
    @Override
    public Double apply(Transaction transaction) throws FirebaseFirestoreException {
        DocumentSnapshot snapshot = transaction.get(sfDocRef);
        double newPopulation = snapshot.getDouble("population") + 1;
        if (newPopulation <= 1000000) {
            transaction.update(sfDocRef, "population", newPopulation);
            return newPopulation;
        } else {
            throw new FirebaseFirestoreException("Population too high",
                    FirebaseFirestoreException.Code.ABORTED);
        }
    }
}).addOnSuccessListener(new OnSuccessListener<Double>() {
    @Override
    public void onSuccess(Double result) {
        Log.d(TAG, "Transaction success: " + result);
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.w(TAG, "Transaction failure.", e);
    }
});
Kotlin
val sfDocRef = db.collection("cities").document("SF")

db.runTransaction { transaction ->
    val snapshot = transaction.get(sfDocRef)
    val newPopulation = snapshot.getDouble("population")!! + 1
    if (newPopulation <= 1000000) {
        transaction.update(sfDocRef, "population", newPopulation)
        newPopulation
    } else {
        throw FirebaseFirestoreException("Population too high",
                FirebaseFirestoreException.Code.ABORTED)
    }
}.addOnSuccessListener { result ->
    Log.d(TAG, "Transaction success: $result")
}.addOnFailureListener { e ->
    Log.w(TAG, "Transaction failure.", e)
}
Java
final DocumentReference docRef = db.collection("cities").document("SF");
ApiFuture<String> transaction =
    db.runTransaction(
        new Transaction.Function<String>() {
          @Override
          public String updateCallback(Transaction transaction) throws Exception {
            DocumentSnapshot snapshot = transaction.get(docRef).get();
            Long newPopulation = snapshot.getLong("population") + 1;
            // conditionally update based on current population
            if (newPopulation <= 1000000L) {
              transaction.update(docRef, "population", newPopulation);
              return "Population increased to " + newPopulation;
            } else {
              throw new Exception("Sorry! Population is too big.");
            }
          }
        });
// Print information retrieved from transaction
System.out.println(transaction.get());
Python
transaction = db.transaction()
city_ref = db.collection(u'cities').document(u'SF')

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    new_population = snapshot.get(u'population') + 1

    if new_population < 1000000:
        transaction.update(city_ref, {
            u'population': new_population
        })
        return True
    else:
        return False

result = update_in_transaction(transaction, city_ref)
if result:
    print(u'Population updated')
else:
    print(u'Sorry! Population is too big.')
Node.js
let cityRef = db.collection('cities').doc('SF');
let transaction = db.runTransaction(t => {
  return t.get(cityRef)
    .then(doc => {
      let newPopulation = doc.data().population + 1;
      if (newPopulation <= 1000000) {
        t.update(cityRef, {population: newPopulation});
        return Promise.resolve('Population increased to ' + newPopulation);
      } else {
        return Promise.reject('Sorry! Population is too big.');
      }
    });
}).then(result => {
  console.log('Transaction success', result);
}).catch(err => {
  console.log('Transaction failure:', err);
});
Go
ref := client.Collection("cities").Doc("SF")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
	doc, err := tx.Get(ref)
	if err != nil {
		return err
	}
	pop, err := doc.DataAt("population")
	if err != nil {
		return err
	}
	newpop := pop.(int64) + 1
	if newpop <= 1000000 {
		return tx.Set(ref, map[string]interface{}{
			"population": pop.(int64) + 1,
		}, firestore.MergeAll)
	}
	return errors.New("population is too big")
})
if err != nil {
	// Handle any errors in an appropriate way, such as returning them.
	log.Printf("An error has occurred: %s", err)
}
PHP
$cityRef = $db->collection('cities')->document('SF');
$transactionResult = $db->runTransaction(function (Transaction $transaction) use ($cityRef) {
    $snapshot = $transaction->snapshot($cityRef);
    $newPopulation = $snapshot['population'] + 1;
    if ($newPopulation <= 1000000) {
        $transaction->update($cityRef, [
            ['path' => 'population', 'value' => $newPopulation]
        ]);
        return true;
    } else {
        return false;
    }
});

if ($transactionResult) {
    printf('Population updated successfully.' . PHP_EOL);
} else {
    printf('Sorry! Population is too big.' . PHP_EOL);
}
C#
DocumentReference cityRef = db.Collection("cities").Document("SF");
bool transactionResult = await db.RunTransactionAsync(async transaction =>
{
    DocumentSnapshot snapshot = await transaction.GetSnapshotAsync(cityRef);
    long newPopulation = snapshot.GetValue<long>("Population") + 1;
    if (newPopulation <= 1000000)
    {
        Dictionary<string, object> updates = new Dictionary<string, object>
        {
            { "Population", newPopulation}
        };
        transaction.Update(cityRef, updates);
        return true;
    }
    else
    {
        return false;
    }
});

if (transactionResult)
{
    Console.WriteLine("Population updated successfully.");
}
else
{
    Console.WriteLine("Sorry! Population is too big.");
}
Ruby
def return_info_transaction project_id:
  # project_id = "Your Google Cloud Project ID"

  firestore = Google::Cloud::Firestore.new project_id: project_id

  city_ref = firestore.doc "cities/SF"

  updated = firestore.transaction do |tx|
    new_population = tx.get(city_ref).data[:population] + 1
    if new_population < 1_000_000
      tx.update city_ref, population: new_population
    else
      false
    end
  end

  if updated
    puts "Population updated!"
  else
    puts "Sorry! Population is too big."
  end
end

事务失败

事务可能会由于以下原因而失败:

  • 事务中包含的读取操作被安排在写入操作之后。读取操作必须始终在写入操作之前进行。
  • 事务读取的文档已被该事务之外的其他操作修改了。在这种情况下,事务将自动重新运行。系统重试事务的次数是有限的。
  • 事务超过了请求大小上限 (10 MiB)。

    事务大小取决于事务修改的文档大小和索引条目大小。对于删除操作,此大小包括目标文档的大小以及为响应操作而删除的索引条目的大小。

失败的事务将返回一个错误,并且不会向数据库中写入任何内容。您不需要回滚事务;Cloud Firestore 会自动执行此操作。

批量写入

如果您不需要读取操作集中的任何文档,可以将多个写入操作作为单个批量操作来执行,该批量操作可包含 set()update()delete() 操作的任意组合。批量写入是以不可分割的原子操作方式完成的,并可以写入多个文档。 以下示例展示了如何构建和提交批量写入操作:

网站
// Get a new write batch
var batch = db.batch();

// Set the value of 'NYC'
var nycRef = db.collection("cities").doc("NYC");
batch.set(nycRef, {name: "New York City"});

// Update the population of 'SF'
var sfRef = db.collection("cities").doc("SF");
batch.update(sfRef, {"population": 1000000});

// Delete the city 'LA'
var laRef = db.collection("cities").doc("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().then(function () {
    // ...
});
Swift
// Get new write batch
let batch = db.batch()

// Set the value of 'NYC'
let nycRef = db.collection("cities").document("NYC")
batch.setData([:], forDocument: nycRef)

// Update the population of 'SF'
let sfRef = db.collection("cities").document("SF")
batch.updateData(["population": 1000000 ], forDocument: sfRef)

// Delete the city 'LA'
let laRef = db.collection("cities").document("LA")
batch.deleteDocument(laRef)

// Commit the batch
batch.commit() { err in
    if let err = err {
        print("Error writing batch \(err)")
    } else {
        print("Batch write succeeded.")
    }
}
Objective-C
// Get new write batch
FIRWriteBatch *batch = [self.db batch];

// Set the value of 'NYC'
FIRDocumentReference *nycRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"NYC"];
[batch setData:@{} forDocument:nycRef];

// Update the population of 'SF'
FIRDocumentReference *sfRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[batch updateData:@{ @"population": @1000000 } forDocument:sfRef];

// Delete the city 'LA'
FIRDocumentReference *laRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"LA"];
[batch deleteDocument:laRef];

// Commit the batch
[batch commitWithCompletion:^(NSError * _Nullable error) {
  if (error != nil) {
    NSLog(@"Error writing batch %@", error);
  } else {
    NSLog(@"Batch write succeeded.");
  }
}];
  
Java
// Get a new write batch
WriteBatch batch = db.batch();

// Set the value of 'NYC'
DocumentReference nycRef = db.collection("cities").document("NYC");
batch.set(nycRef, new City());

// Update the population of 'SF'
DocumentReference sfRef = db.collection("cities").document("SF");
batch.update(sfRef, "population", 1000000L);

// Delete the city 'LA'
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
    @Override
    public void onComplete(@NonNull Task<Void> task) {
        // ...
    }
});
Kotlin
// Get a new write batch
val batch = db.batch()

// Set the value of 'NYC'
val nycRef = db.collection("cities").document("NYC")
batch.set(nycRef, City())

// Update the population of 'SF'
val sfRef = db.collection("cities").document("SF")
batch.update(sfRef, "population", 1000000L)

// Delete the city 'LA'
val laRef = db.collection("cities").document("LA")
batch.delete(laRef)

// Commit the batch
batch.commit().addOnCompleteListener {
    // ...
}
Java
// Get a new write batch
WriteBatch batch = db.batch();

// Set the value of 'NYC'
DocumentReference nycRef = db.collection("cities").document("NYC");
batch.set(nycRef, new City());

// Update the population of 'SF'
DocumentReference sfRef = db.collection("cities").document("SF");
batch.update(sfRef, "population", 1000000L);

// Delete the city 'LA'
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(laRef);

// asynchronously commit the batch
ApiFuture<List<WriteResult>> future = batch.commit();
// ...
// future.get() blocks on batch commit operation
for (WriteResult result :future.get()) {
  System.out.println("Update time : " + result.getUpdateTime());
}
Python
batch = db.batch()

# Set the data for NYC
nyc_ref = db.collection(u'cities').document(u'NYC')
batch.set(nyc_ref, {u'name': u'New York City'})

# Update the population for SF
sf_ref = db.collection(u'cities').document(u'SF')
batch.update(sf_ref, {u'population': 1000000})

# Delete LA
la_ref = db.collection(u'cities').document(u'LA')
batch.delete(la_ref)

# Commit the batch
batch.commit()
Node.js
// Get a new write batch
let batch = db.batch();

// Set the value of 'NYC'
let nycRef = db.collection('cities').doc('NYC');
batch.set(nycRef, {name: 'New York City'});

// Update the population of 'SF'
let sfRef = db.collection('cities').doc('SF');
batch.update(sfRef, {population: 1000000});

// Delete the city 'LA'
let laRef = db.collection('cities').doc('LA');
batch.delete(laRef);

// Commit the batch
return batch.commit().then(function () {
  // ...
});
Go
// Get a new write batch.
batch := client.Batch()

// Set the value of "NYC".
nycRef := client.Collection("cities").Doc("NYC")
batch.Set(nycRef, map[string]interface{}{
	"name": "New York City",
})

// Update the population of "SF".
sfRef := client.Collection("cities").Doc("SF")
batch.Set(sfRef, map[string]interface{}{
	"population": 1000000,
}, firestore.MergeAll)

// Delete the city "LA".
laRef := client.Collection("cities").Doc("LA")
batch.Delete(laRef)

// Commit the batch.
_, err := batch.Commit(ctx)
if err != nil {
	// Handle any errors in an appropriate way, such as returning them.
	log.Printf("An error has occurred: %s", err)
}
PHP
$batch = $db->batch();

# Set the data for NYC
$nycRef = $db->collection('cities')->document('NYC');
$batch->set($nycRef, [
    'name' => 'New York City'
]);

# Update the population for SF
$sfRef = $db->collection('cities')->document('SF');
$batch->update($sfRef, [
    ['path' => 'population', 'value' => 1000000]
]);

# Delete LA
$laRef = $db->collection('cities')->document('LA');
$batch->delete($laRef);

# Commit the batch
$batch->commit();
C#
WriteBatch batch = db.StartBatch();

// Set the data for NYC
DocumentReference nycRef = db.Collection("cities").Document("NYC");
Dictionary<string, object> nycData = new Dictionary<string, object>
{
    { "name", "New York City" }
};
batch.Set(nycRef, nycData);

// Update the population for SF
DocumentReference sfRef = db.Collection("cities").Document("SF");
Dictionary<string, object> updates = new Dictionary<string, object>
{
    { "Population", 1000000}
};
batch.Update(sfRef, updates);

// Delete LA
DocumentReference laRef = db.Collection("cities").Document("LA");
batch.Delete(laRef);

// Commit the batch
await batch.CommitAsync();
Ruby
firestore.batch do |b|
  # Set the data for NYC
  b.set "cities/NYC", name: "New York City"

  # Update the population for SF
  b.update "cities/SF", population: 1_000_000

  # Delete LA
  b.delete "cities/LA"
end

批量写入最多可以包含 500 个操作,其中每个操作单独计入您的 Cloud Firestore 使用量。在写入操作中,字段会将 serverTimestamparrayUnionincrement 等每个计数转换为附加操作。

与事务一样,批量写入是以原子操作方式完成的。与事务不同,批量写入不需要确保读取文档保持未修改状态,这样可减少出现失败的情况。它们不会受重试或因重试次数过多而导致的失败的影响。即使用户的设备处于离线状态,批量写入也会执行。

对原子操作进行数据验证

对于移动/网页客户端库,您可以使用 Cloud Firestore 安全规则验证数据。您可以确保相关文档始终以原子方式更新,并始终作为事务或批量写入的一部分。在一组操作完成后,但在 Cloud Firestore 提交这些操作之前,您可以使用 getAfter() 安全规则函数访问并验证文档的状态。

例如,假设 cities 示例的数据库还包含一个 countries 集合。每个 country 文档都使用 last_updated 字段来跟踪与该国家/地区相关的任何城市的最近更新时间。以下安全规则要求对 city 文档进行更新时,必须也以原子方式更新相关国家/地区的 last_updated 字段:

service cloud.firestore {
  match /databases/{database}/documents {
    // If you update a city doc, you must also
    // update the related country's last_updated field.
    match /cities/{city} {
      allow write: if request.auth.uid != null &&
        getAfter(
          /databases/$(database)/documents/countries/$(request.resource.data.country)
        ).data.last_updated == request.time;
    }

    match /countries/{country} {
      allow write: if request.auth.uid != null;
    }
  }
}

安全规则限制

在事务或批量写入的安全规则中,除了针对批量操作中每个单一文档操作的常规调用上限(10 次)以外,整个原子操作的文档访问调用次数上限为 20 次。

例如,考虑某个聊天应用的以下规则:

service cloud.firestore {
  match /databases/{db}/documents {
    function prefix() {
      return /databases/{db}/documents;
    }
    match /chatroom/{roomId} {
      allow read, write: if roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats
                            || exists(/$(prefix())/admins/$(request.auth.uid));
    }
    match /users/{userId} {
      allow read, write: if userId == request.auth.uid
                            || exists(/$(prefix())/admins/$(request.auth.uid));
    }
    match /admins/{userId} {
      allow read, write: if exists(/$(prefix())/admins/$(request.auth.uid));
    }
  }
}

以下代码段展示了用于一些数据访问模式的文档访问调用次数:

// 0 document access calls used, because the rules evaluation short-circuits
// before the exists() call is invoked.
db.collection('user').doc('myuid').get(...);

// 1 document access call used. The maximum total allowed for this call
// is 10, because it is a single document request.
db.collection('chatroom').doc('mygroup').get(...);

// Initializing a write batch...
var batch = db.batch();

// 2 document access calls used, 10 allowed.
var group1Ref = db.collection("chatroom").doc("group1");
batch.set(group1Ref, {msg: "Hello, from Admin!"});

// 1 document access call used, 10 allowed.
var newUserRef = db.collection("users").doc("newuser");
batch.update(newUserRef, {"lastSignedIn": new Date()});

// 1 document access call used, 10 allowed.
var removedAdminRef = db.collection("admin").doc("otheruser");
batch.delete(removedAdminRef);

// The batch used a total of 2 + 1 + 1 = 4 document access calls, out of a total
// 20 allowed.
batch.commit();