You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter项目Firebase Firestore事务并发操作时数据偶发覆盖丢失问题

问题根因

你遇到的并发修改丢失问题核心是Firestore事务的使用方法错误:

  • 事务内部的文档读取操作必须通过transaction.get()执行,不能直接调用DocumentReference.get()。只有通过事务对象读取的文档才会被纳入冲突检测范围,当你读取后文档被其他请求修改时,事务会自动重试保证原子性,你当前的写法跳过了冲突检测步骤,并发场景下后提交的事务会直接覆盖前序修改。
  • saveTask方法的set操作没有开启merge,直接全量覆盖文档,进一步放大了修改丢失的概率。
解决方案

1. 修复事务读取逻辑

将两个事务内部的文档读取全部替换为transaction.get(),开启冲突检测。

2. 给saveTask的set操作添加merge配置

避免全量覆盖文档,仅更新修改过的字段。

3. 校验toJson序列化逻辑

确保StudentGroup.toJson()不会将未修改的字段序列化为null或默认值,避免merge时意外覆盖现有有效数据。

4. 可选优化(高并发场景建议)

Firestore单文档最佳写入频率为≤1次/秒,如果你后续操作频率升高,可以将计时器、任务数据拆分到不同的子文档存储,降低单文档并发冲突概率。

修改后代码示例

saveTask 修复版

Future<Result> saveTask({required String sessionId, required String studentGroupId,
  required Task task}) async {
  print("trying to save task <$task>.");
  try {
    return await _firebaseFirestore.runTransaction((transaction) async {
      final studentGroupRef = _getStudentGroupDocumentReference(
          sessionId: sessionId,
          studentGroupId: studentGroupId
      );
      // 替换为transaction.get() 纳入冲突检测
      final sessionGroupDoc = await transaction.get(studentGroupRef);

      if (!sessionGroupDoc.exists) {
        return Result.error("student group not exists");
      }

      final sessionGroup = StudentGroup.fromSnapshot(sessionGroupDoc);
      sessionGroup.game.saveTask(task);
      // 添加merge配置
      transaction.set(
        studentGroupRef, 
        sessionGroup.toJson(),
        SetOptions(merge: true)
      );
    })
    .then((value) => taskFunction(true))
    .catchError((error) => taskFunction(false));
  } catch (error) {
    return Result.error("Error couldn't save task");
  }
}

updateTaskTimer 修复版

Future<Result> updateTaskTimer({required String sessionId,
  required String studentGroupId, required Duration duration}) async {
  print("trying to update first task timer");
  try {
    return await _firebaseFirestore.runTransaction((transaction) async {
      final studentGroupRef = _getStudentGroupDocumentReference(
          sessionId: sessionId,
          studentGroupId: studentGroupId
      );
      // 替换为transaction.get() 纳入冲突检测
      final sessionGroupDoc = await transaction.get(studentGroupRef);

      if (!sessionGroupDoc.exists) {
        return Result.error("student group not exists");
      }

      final sessionGroup = StudentGroup.fromSnapshot(sessionGroupDoc);
      switch (sessionGroup.game.gameStage) {
        case GameStage.First:
          sessionGroup.game.stages.first.duration = duration.inSeconds;
          break;
        case GameStage.Second:
          sessionGroup.game.stages[1].duration = duration.inSeconds;
          break;
        case GameStage.Third:
          sessionGroup.game.stages[2].duration = duration.inSeconds;
          break;
        case GameStage.Fourth:
          sessionGroup.game.stages[3].duration = duration.inSeconds;
          break;
        case GameStage.Fifth:
          sessionGroup.game.stages[4].duration = duration.inSeconds;
          break;
      }
      transaction.set(
          studentGroupRef,
          sessionGroup.toJson(),
          SetOptions(merge: true)
      );

      print("Did I finish task 4? ${sessionGroup.game.stages.first.tasks[3].isFinished}");

    })
    .then((value) => timerFunction(true))
    .catchError((error) => timerFunction(false));
  } catch (error) {
    return Result.error("Error couldn't update task timer");
  }
}

内容的提问来源于stack exchange,提问作者ShadeToD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 16:57:00