Flutter+Firebase Firestore并发更新字段冲突及锁机制需求
解决Firestore并发更新竞态问题
问题场景与现象
业务需求:更新Firestore集合字段前需检查字段是否为空
- 字段为空:更新字段并提示成功
- 字段不为空:仅提示更新失败
并发时出现异常:两名用户几乎同时操作时,两人都收到成功提示,但最终只有后操作的用户数据生效,前用户的更新被覆盖。
问题根源
原有代码采用「先读取字段状态 → 再判断更新」的非原子操作,两步之间存在时间窗口:
- 用户1读取字段为空,开始执行更新
- 用户2在用户1更新完成前,同样读取到字段为空,也执行更新
- 用户2的更新覆盖了用户1的结果
解决方案
Firestore提供两种原子性操作方案,确保「检查+更新」的操作不可拆分,彻底解决竞态问题:
方案1:使用Firestore事务(Transaction)
事务会将「读取文档 → 判断条件 → 更新文档」整个流程封装为原子操作,若事务执行期间文档被其他用户修改,Firestore会自动重试事务,直到成功或超时。
修改后的assignBooking函数:
Future<bool> assignBooking({ required String bid, required String status, required String ownerId, required String equipmentid, required String model, required String bookingType, }) async { try { return await FirebaseFirestore.instance.runTransaction<bool>((transaction) async { final bookingDoc = allBookings.doc(bid); final bookingData = await transaction.get(bookingDoc); // 检查字段是否为空 if (bookingData['assigned_equipment_owner_id'] == null || bookingData['assigned_equipment_owner_id'].isEmpty) { // 事务内执行更新 transaction.update(bookingDoc, { "status": status, 'assigned_equipment_owner_id': ownerId, 'equipment_id': equipmentid, 'model': model, 'booking_type': bookingType, }); return true; } else { return false; } }); } catch (e) { // 事务失败(如超时、权限问题),返回更新失败 return false; } }
方案2:使用条件更新(原子写入)
直接在更新操作中添加前置条件,仅当字段为空时才执行更新,整个更新操作是原子性的,无需单独读取文档。
修改后的assignBooking函数:
Future<bool> assignBooking({ required String bid, required String status, required String ownerId, required String equipmentid, required String model, required String bookingType, }) async { try { await allBookings.doc(bid).update( { "status": status, 'assigned_equipment_owner_id': ownerId, 'equipment_id': equipmentid, 'model': model, 'booking_type': bookingType, }, SetOptions( // 添加条件:字段为空或不存在时才更新 condition: Filter.or( Filter.isNull('assigned_equipment_owner_id'), Filter.equalTo('assigned_equipment_owner_id', ''), ), ), ); return true; } on FirebaseException catch (e) { // 条件不满足时抛出failed-precondition异常,返回更新失败 if (e.code == 'failed-precondition') { return false; } // 其他异常重新抛出 rethrow; } catch (e) { return false; } }
注意事项
- 方案1的事务适合需要基于当前文档状态做复杂判断的场景,Firestore会自动重试冲突的事务
- 方案2的条件更新更轻量,适合简单的前置条件判断,无需额外读取文档
- 两种方案都能确保操作的原子性,彻底避免并发覆盖问题
内容的提问来源于stack exchange,提问作者Sindu
相关产品推荐
相关产品推荐

