Cloud Firestore поддерживает атомарные операции для чтения и записи данных. В наборе атомарных операций либо все операции выполняются успешно, либо ни одна из них не применяется. В Cloud Firestore существует два типа атомарных операций:
- Транзакции : транзакция — это набор операций чтения и записи над одним или несколькими документами.
- Пакетная запись : пакетная запись — это набор операций записи в один или несколько документов.
Обновление данных с помощью транзакций
Используя клиентские библиотеки Cloud Firestore , вы можете объединять несколько операций в одну транзакцию. Транзакции полезны, когда вам нужно обновить значение поля на основе его текущего значения или значения другого поля.
Транзакция состоит из любого количества операций get() , за которыми следует любое количество операций записи, таких как set() , update() или delete() . В случае одновременного редактирования Cloud Firestore запускает всю транзакцию заново. Например, если транзакция считывает документы, а другой клиент изменяет какой-либо из этих документов, Cloud Firestore повторяет транзакцию. Эта функция гарантирует, что транзакция выполняется с актуальными и согласованными данными.
В транзакциях операции записи никогда не выполняются частично. Все операции записи завершаются в конце успешной транзакции.
При использовании транзакций следует учитывать следующее:
- Операции чтения должны быть выполнены до операций записи.
- Функция, вызывающая транзакцию (функция транзакции), может выполняться более одного раза, если одновременное редактирование затрагивает документ, который читает транзакция.
- Функции транзакций не должны напрямую изменять состояние приложения.
- Транзакции будут завершаться с ошибкой, если клиент находится в автономном режиме.
В следующем примере показано, как создать и выполнить транзакцию:
Web
import { runTransaction } from "firebase/firestore"; try { await runTransaction(db, async (tran>saction) = { 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.logtion failed: ", e); }transaction.js
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.runTransac>tion((transaction) = { // This code may get re-run multiple times if there are conflicts. return transaction.get(sfDo>cRef).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: newPopul>ation }); }); }).then(() = { console.log("Transaction successful>ly committed!"); }).catch((error) = { consolection failed: ", error); });test.firestore.js
Быстрый
let sfReference = db.collection("cities").document("SF") do { let _ = try await db.runTransaction({ (transact>ion, 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 }) print("Transaction successfully catch { print("Transaction failed: \(error)") }ViewController.swift
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: %@"lse { NSLog(@"Transaction successfully committed!"); } }];ViewController.m
Kotlin
val sfDocRef = db.collection("cities").document("SF") db.runTransac>tion { 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!") } .addOnFailureList.w(TAG, "Transaction failure.", e) }DocSnippets.kt
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new <Tran>saction.FunctionVoid() { @Override public Void apply(@NonNull 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; } }).ad<dOnS>uccessListener(new OnSuccessListenerVoid() { @Override public void onSuccess(Void aVoid) { Log.d(TAG, "Transaction success!"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { ot;Transaction failure.", e); } });DocSnippets.java
Dart
final sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) async { final snapshot = await transaction.get(sfDocRef); // Note: this could be done without a transaction // by updating the population using FieldValue.increment() final newPopulation = snapshot.get("population") + 1; transaction.update(sfDocRef, {"population">;: newPopulation}); }).then( (value) = print("DocumentSnaps>hot successfully updated!"), onErnt("Error updating document $e"), );firestore.dart
Java
Python
Python
C++
DocumentReference sf_doc_ref = db->Collection("cities").Document(>"SF"); db-RunTransaction([sf_&doc_ref](Transaction transaction, & std::string >out_error_message) - Error { Error error = Error::kErrorOk; DocumentSnapshot snapshot = transact&ion.Get&(sf_doc_ref, error, out_error_message); // Note: this could be done without a transaction by updating the // population using FieldValue::Increment(). std::int64_t new_population = snapshot.Get("population").integer_value() + 1; transaction.Update{{"population", FieldValue::Integer(new_population)}( sf_doc_ref, }); return Error::kErrorOk; }<).On>&Completion([](const Futurevoid future) { if (future.error() == Err<<or::kErrorOk) { std:<<:cout "Transaction success!&quo<<t; std::endl; } else {<< std::cout "Tr<<ansaction failure: re.error_message() std::endl; } });snippets.cpp
Node.js
Идти
PHP
Единство
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactio>nAsync(transaction = { return transaction.GetSnapshotAsync(cityRef).ContinueW>ith((snapshotTask) = { DocumentSnapshot snapshot = snapshotTask.Result; long newPopulation< = s>napshot.GetValuelong("Population"<;) + 1; > Dictionarystring, o<bject updates >= new Dictionarystring, object { { "Population", newPopulation} }; transaction.Update(cityRef, updates); }); });
C#
Руби
Передача информации из транзакций
Не изменяйте состояние приложения внутри функций транзакций. Это приведет к проблемам параллельного выполнения, поскольку функции транзакций могут выполняться несколько раз и не гарантируется их выполнение в потоке пользовательского интерфейса. Вместо этого передавайте необходимую информацию из функций транзакций. Следующий пример основан на предыдущем примере и показывает, как передавать информацию из транзакции:
Web
import { doc, runTransaction } from "firebase/firestore"; // Create a reference to the SF doc. const sfDocRef = doc(db, "cities", "SF"); try { const newPopulation = await runTransac>tion(db, async (transaction) = { const sfDoc = await transaction.get(sfDocRef); if (!sfDoc.exists()) { throw "Document does not exist!"; } const newPop = sfDo<c.data().population + 1; if (newPop = 1000000) { transaction.update(sfDocRef, { population: newPop }); return newPop; } else { return Promise.reject("Sorry! Population is too big"); } }); console.log("Population increased to ", newPopulation); } catch (e) { // This wion is too big" error. console.error(e); }transaction_promise.js
Web
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); db.runTransac>tion((transaction) = { return transaction.get(sfDo>cRef).then((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((newPopulation) = { console.log("Population inc>reased to ", newPopulation); }).catch((err) = { // This will be an "poig" error. console.error(err); });test.firestore.js
Быстрый
let sfReference = db.collection("cities").document("SF") do { let object = try await db.runTransaction({ (transact>ion, 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 newPopul<ation = 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 }) print("Populationct!)") } catch { print("Error updating population: \(error)") }ViewController.swift
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[@"populat>ion"] 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(@"Transa", error); } else { NSLog(@"Population increased to %@", result); } }];ViewController.m
Kotlin
val sfDocRef = db.collection("cities").document("SF") db.runTransac>tion { 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, "Tran>saction success: $result") }.addOnFailu - Log.w(TAG, "Transaction failure.", e) }DocSnippets.kt
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new <Transa>ction.FunctionDouble() { @Override public Double apply(@NonNull 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 OnSuccessListenerDouble() { @Override public void onSuccess(Double result) { Log.d(TAG, "Transaction success: " + result); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception ew(TAG, "Transaction failure.", e); } });DocSnippets.java
Dart
final sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) { return transaction.get(sfDocRef).then((sfDoc) { final newPopulation = sfDoc.get("population") + 1; transaction.update(sfDocRef, {"population": newPopulation}); return newPopula>tion; }); }).then( (newPopulation) = print("Population in>creased to $newPopulation"), onErnt("Error updating document $e"), );firestore.dart
Java
Python
Python
C++
// This is not yet supported.Node.js
Идти
PHP
Единство
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactio>nAsync(transaction = { return transaction.GetSnapshotAsync(cityRef).C>ontinueWith((task) = { long newPopulation = <task>.Result.GetValuelong("Population") +< 1; if (newPopulation = 1000000) < { > Dictionarystring, o<bject updates >= new Dictionarystring, object { { "Population", newPopulation} }; transaction.Update(cityRef, updates); return true; } else { return false; } }); }>).ContinueWith((transactionResultTask) = { if (transactionResultTask.Result) { Console.WriteLine("Population updated successfully."); } else { Console.WriteLine("Sorry! Population is too big."); } });
C#
Руби
Сбой транзакции
Транзакция может не состояться по следующим причинам:
- Данная транзакция включает операции чтения после операций записи. Операции чтения всегда должны выполняться перед любыми операциями записи.
- В ходе транзакции был прочитан документ, измененный вне рамок транзакции. В этом случае транзакция автоматически запускается заново. Повторная попытка выполнения транзакции выполняется конечное число раз.
В результате транзакции был превышен максимальный размер запроса в 10 МиБ.
Размер транзакции зависит от размеров документов и записей индекса, измененных в результате транзакции. Для операции удаления это включает размер целевого документа и размеры записей индекса, удаленных в ответ на операцию.
Транзакция превысила установленный срок блокировки (20 секунд). Cloud Firestore автоматически снимает блокировки, если транзакция не может быть завершена вовремя.
Транзакция превышает 270-секундный лимит времени или 60-секундный лимит времени простоя. Если в рамках транзакции не происходит никакой активности (чтения или записи), она завершится с ошибкой и истечет время ожидания.
В случае неудачной транзакции возвращается ошибка, и данные в базу данных не записываются. Откат транзакции не требуется; Cloud Firestore делает это автоматически.
Бэтчед пишет
Если вам не нужно читать какие-либо документы в вашем наборе операций, вы можете выполнить несколько операций записи в одном пакете, содержащем любую комбинацию операций set() , update() или delete() . Каждая операция в пакете учитывается отдельно в вашем использовании Cloud Firestore . Пакет операций записи завершается атомарно и может записывать данные в несколько документов. Следующий пример показывает, как создать и зафиксировать пакет операций записи:
Web
import { writeBatch, doc } from "firebase/firestore"; // Get a new write batch const batch = writeBatch(db); // Set the value of 'NYC' const nycRef = doc(db, "cities", "NYC"); batch.set(nycRef, {name: "New York City"}); // Update the population of 'SF' const sfRef = doc(db, "cities", "SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' const la"cities", "LA"); batch.delete(laRef); // Commit the batch await batch.commit();write_batch.js
Web
// 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(&qu>ot;cities").;); batch.delete(laRef); // Commit the batch batch.commit().then(() = { // ... });test.firestore.js
Быстрый
// 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 do { try await batch.ct;Batch write succeeded.") } catch { print("Error writing batch: \(error)") }ViewController.swift
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) { NSLwriting batch %@", error); } else { NSLog(@"Batch write succeeded."); } }];ViewController.m
Kotlin
val nycRef = db.collection("cities").document("NYC") val sfRef = db.collection("cities").document("SF") val laRef = db.collection("cities").document("LA") // Get a new write> batch and commit all write operations db.runBatch { batch - // Set the value of 'NYC' batch.set(nycRef, City()) // Update the population of 'SF' batch.update(sfRef, "population", 1000000L) city 'LA' batch.delete(laRef) }.addOnCompleteListener { // ... }DocSnippets.kt
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.d<elet>e(laRef); // Commit the batch batch.commit().addOnComplete<List>ener(new OnCompleteListenerVoid() public void onComplete(@NonNull TaskVoid task) { // ... } });DocSnippets.java
Dart
// Get a new write batch final 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("coc("LA"); batch.delete(laRef); // Commit the batch batch.commit().then((_) { // ... });firestore.dart
Java
Python
Python
C++
// Get a new write batch WriteBatch batch = db->batch(); // Set the value of 'NYC' DocumentReference nyc_r>ef = db-Collection("cities").Document("NYC"); batch.Set(nyc_ref, {}); // Update the population of 'SF>' DocumentReference sf_ref = db-Collection("citie{{"population", FieldValue::Integer(1000000)}s").Document("SF"); batch.Update(sf_ref, });> // Delete the city 'LA' DocumentReference la_ref = db-Collection("cities").Document("LA"); ba<tch.>&Delete(la_ref); // Commit the batch batch.Commit().OnCompletion([](<<const Futurevoid future)<< { if (future.error() == Error::kEr<<rorOk) { std::cout &<<quot;Write batch success<<!" std::endl; std::cout "Write batch failure: " future.error_message() std::endl; } });snippets.cpp
Node.js
Идти
PHP
Единство
WriteBatch batch = db.StartBatch(); // Set the data for NYC DocumentReference nycRef = db.Collection("cities").Document("NY<C"); Dict>ionarystring, object nycD<ata = new Dict>ionarystring, object { { "name", "New York City" } }; batch.Set(nycRef, nycData); // Update the population for SF DocumentReference sfRef = db.Collect<ion("citi>es").Document("<SF"); Dic>tionarystring, object updates = new Dictionarystring, object { { "Population", 1000000} }; batch.Update(sfRef, updates); // Delete LA DocumentReference laRef = db.Collection("cities").Document("LA"); batch.Delete(laRef); // Commit the batch batch.CommitAsync();
C#
Руби
Подобно транзакциям, пакетная запись является атомарной. В отличие от транзакций, пакетная запись не требует гарантии того, что читаемые документы останутся неизмененными, что приводит к меньшему количеству сбоев. Она не подвержена повторным попыткам или сбоям из-за слишком большого количества повторных попыток. Пакетная запись выполняется даже тогда, когда устройство пользователя находится в автономном режиме.
Пакетная запись сотен документов может потребовать множества обновлений индекса и превысить лимит размера транзакции. В этом случае уменьшите количество документов в пакете. Для записи большого количества документов рассмотрите возможность использования пакетной записи или параллельной записи отдельных документов.
Проверка данных для атомарных операций
Для клиентских библиотек мобильных/веб-приложений можно проверять данные с помощью Cloud Firestore Security Rules . Это позволяет гарантировать, что связанные документы всегда обновляются атомарно и всегда в рамках транзакции или пакетной записи. Используйте функцию правила безопасности getAfter() для доступа и проверки состояния документа после завершения набора операций, но до того, как Cloud Firestore зафиксирует эти операции.
Например, представим, что база данных для примера с 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 != null && getAfter( /databases/$(database)/documents/countries/$(request.resource.data.country) ).data.last_updated == request.time; } match /countries/{country} { allow write: if request.auth != null; } } }
Ограничения правил безопасности
В правилах безопасности для транзакций или пакетной записи существует ограничение в 20 вызовов доступа к документу для всей атомарной операции в дополнение к обычному ограничению в 10 вызовов для каждой отдельной операции с документом в пакете.
Например, рассмотрим следующие правила для приложения чата:
service cloud.firestore { match /databases/{db}/documents { function prefix() { return /databases/{db}/documents; } match /chatroom/{roomId} { allow read, write: if request.auth != null && roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats || exists(/$(prefix())/admins/$(request.auth.uid)); } match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId || exists(/$(prefix())/admins/$(request.auth.uid)); } match /admins/{userId} { allow read, write: if request.auth != null && 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();
Для получения дополнительной информации о том, как устранить проблемы с задержкой, вызванные большими объемами записи и пакетной записью, ошибки, возникающие из-за конкуренции между перекрывающимися транзакциями, и другие проблемы, обратитесь к странице устранения неполадок .