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

Firebase实时数据库是否支持类似Firestore的‘全有或全无’事务?

Firebase实时数据库的原子事务实现与匹配逻辑优化

Firebase实时数据库确实支持类似Firestore的全有或全无原子事务,通过runTransaction方法实现。这个方法能保证事务内的所有读写操作要么全部成功,要么全部回滚,而且事务执行期间,读取到的数据是一致的,不受外部并发修改的影响。

现有代码的核心问题

你当前的实现没有利用实时数据库的事务机制,存在两个关键问题:

  • 读取match_queue快照后,后续的匹配和数据库操作是分离的,并发场景下(比如新节点ghi在abc和def匹配过程中加入),新的云函数实例可能读取到旧的快照,导致ghi尝试和abc/def重复匹配
  • 创建匹配、更新用户、删除队列节点这些操作不是原子执行的,可能出现部分操作成功、部分失败的不一致状态

优化后的事务实现方案

要解决重复匹配和原子性问题,需要把整个匹配逻辑包裹在事务中,并且在事务内先标记候选节点为"正在匹配",避免被其他并发事务选中。以下是修改后的代码:

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";

admin.initializeApp();
const db = admin.database();
const MATCH_QUEUE_REF = "match_queue";

export const matchPlayers = functions.database
  .ref(`${MATCH_QUEUE_REF}/{newEntryId}`)
  .onCreate(async (createdChild) => {
    const newEntryRef = createdChild.ref;

    // 启动事务处理匹配逻辑
    await db.runTransaction(async (transaction) => {
      // 先获取当前新节点的最新快照,防止被其他事务修改
      const newEntrySnapshot = await transaction.get(newEntryRef);
      if (!newEntrySnapshot.exists() || newEntrySnapshot.val().matched || newEntrySnapshot.val().matching) {
        // 节点已被匹配、删除或正在匹配,终止事务
        return;
      }

      // 获取整个match_queue的快照,筛选可用的匹配对象
      const queueSnapshot = await transaction.get(db.ref(MATCH_QUEUE_REF));
      let matchCandidateRef: admin.database.Reference | null = null;

      queueSnapshot.forEach((childSnapshot) => {
        const childVal = childSnapshot.val();
        // 排除自己、已匹配、正在匹配的节点
        if (childSnapshot.key !== createdChild.key && !childVal.matched && !childVal.matching) {
          matchCandidateRef = childSnapshot.ref;
          return true; // 找到第一个匹配对象后退出循环
        }
        return false;
      });

      if (!matchCandidateRef) {
        // 没有找到匹配对象,事务结束
        return;
      }

      // 获取匹配候选节点的最新快照
      const candidateSnapshot = await transaction.get(matchCandidateRef);
      if (!candidateSnapshot.exists() || candidateSnapshot.val().matched || candidateSnapshot.val().matching) {
        // 候选节点已被处理,终止事务
        return;
      }

      // 1. 标记两个节点为正在匹配,锁定资源
      transaction.update(newEntryRef, { matching: true });
      transaction.update(matchCandidateRef, { matching: true });

      // 2. 创建新的匹配记录
      const matchRef = db.ref("matches").push();
      const matchId = matchRef.key;
      const matchData = {
        player_1_id: createdChild.val().id,
        player_2_id: candidateSnapshot.val().id,
      };
      transaction.set(matchRef, matchData);

      // 3. 更新用户的当前匹配ID
      transaction.update(db.ref(`users/${createdChild.val().id}`), { current_match_id: matchId });
      transaction.update(db.ref(`users/${candidateSnapshot.val().id}`), { current_match_id: matchId });

      // 4. 删除队列中的两个节点(或标记为matched,根据需求选择)
      transaction.remove(newEntryRef);
      transaction.remove(matchCandidateRef);
    });

    return null;
  });

关键逻辑说明

  • 事务原子性:所有数据库操作(标记状态、创建匹配、更新用户、删除节点)都在runTransaction内执行,要么全部成功,要么全部回滚
  • 节点锁定:通过设置matching: true标记正在匹配的节点,其他事务读取到这个标记后会跳过该节点,避免重复匹配
  • 冲突重试:如果事务执行期间,其他并发操作修改了相关节点,实时数据库会自动重试事务,确保最终数据一致性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:09:59