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
相关产品推荐
相关产品推荐

