为何Firestore批量操作在arrayRemove()执行失败时仍能完成?
问题:Firestore批量操作中arrayRemove未匹配到对象时为何不触发批量失败?
场景与实现代码
需求是更新Firestore数组中的嵌套对象:通过arrayRemove()移除原对象,再用arrayUnion()添加更新后的对象,将这两个操作放在批量操作中,预期任一操作失败则整个批量操作回滚。
Swift实现代码如下:
let batch = db.batch() let docRef = db.collection("events").document("20220715") let encodedRemove = try! Firestore.Encoder().encode(originalEvent) let encodedUpdate = try! Firestore.Encoder().encode(updatedEvent) batch.updateData(["dayEvent": FieldValue.arrayRemove([encodedRemove])], forDocument: docRef) batch.updateData(["dayEvent": FieldValue.arrayUnion([encodedUpdate])], forDocument: docRef) batch.commit() { err in if let err = err { print("Error writing batch \(err)") } else { print("Batch write succeeded.") } }
异常现象
正常流程下操作符合预期:移除原对象并添加更新后的对象。但如果本地保存原对象后,服务器上的该对象被修改,此时批量操作依然会显示成功:
arrayRemove()因找不到匹配对象,未执行移除动作arrayUnion()成功添加了更新后的对象
最终数组中会同时存在原对象(已被服务器修改的版本)和更新后的对象,出现数据不一致的问题。
核心疑问
根据Firestore官方文档,批量操作的特性是“要么全部操作成功,要么都不执行”,为何上述场景中批量操作仍能完成?
原因说明
Firestore的arrayRemove()操作本身不会因找不到匹配对象而抛出错误,它只会静默地不执行任何变更。而批量操作的“原子性”仅保证所有合法操作要么全部执行,要么全部不执行——这里的“合法”指操作本身符合Firestore的规则(比如权限合规、数据格式正确、文档存在等),而非操作必须产生预期的变更效果。
因为arrayRemove()找不到匹配对象属于“合法但无变更”的情况,而非操作失败,所以整个批量操作会正常完成,不会触发回滚。
如果要实现“原对象不存在则终止操作”的逻辑,不能使用批量操作,而是需要用事务:在事务中先读取文档,确认原对象的状态符合预期后,再执行移除和添加操作;若读取时发现原对象已被修改,事务会自动重试或抛出错误,确保数据一致性。
事务实现示例:
db.runTransaction({ (transaction, errorPointer) -> Any? in let docRef = self.db.collection("events").document("20220715") // 读取当前文档数据 guard let snapshot = try? transaction.getDocument(docRef) else { errorPointer?.pointee = NSError(domain: "AppError", code: -1, userInfo: [NSLocalizedDescriptionKey: "读取文档失败"]) return nil } // 获取数组数据 guard var dayEvents = snapshot.data()?["dayEvent"] as? [[String: Any]] else { errorPointer?.pointee = NSError(domain: "AppError", code: -2, userInfo: [NSLocalizedDescriptionKey: "未找到dayEvent数组"]) return nil } // 编码原对象并检查是否存在 let encodedRemove = try! Firestore.Encoder().encode(originalEvent) guard dayEvents.contains(where: { $0 == encodedRemove }) else { errorPointer?.pointee = NSError(domain: "AppError", code: -3, userInfo: [NSLocalizedDescriptionKey: "原对象不存在"]) return nil } // 执行移除和添加操作 dayEvents.removeAll(where: { $0 == encodedRemove }) let encodedUpdate = try! Firestore.Encoder().encode(updatedEvent) dayEvents.append(encodedUpdate) transaction.setData(["dayEvent": dayEvents], forDocument: docRef) return nil }) { (_, error) in if let error = error { print("事务执行失败: \(error)") } else { print("事务执行成功!") } }
内容的提问来源于stack exchange,提问作者ericboles
相关产品推荐
相关产品推荐

