Firestore事务疑似允许并发编辑,出现储物柜编号重复分配问题
问题背景
我们长期使用以下Cloud Function,从单个Firestore文档内的唯一储物柜编号数组中为用户分配单个编号:
function grabLocker(){ return admin.firestore().runTransaction(async (t) => { var lockerDoc = await t.get(lockerref); if(lockerDoc && lockerDoc.data()){ var lockerArr = lockerDoc.data().lockers ? lockerDoc.data().lockers : []; if (lockerArr.length > 0){ var randomIndex = Math.floor(Math.random()*((lockerArr.length - 1)-0+1)+0); var newLockers = lockerArr.slice(0,randomIndex).concat(lockerArr.slice(randomIndex + 1)); var userLocker = lockerArr.slice(randomIndex, randomIndex + 1); t.update(lockerref, {'lockers': newLockers}); return {status: 'succes', locker: userLocker[0]}; } return {status: 'empty'}; } return {status: 'empty'}; }); }

在最近的活动中,该脚本为600名申请储物柜的用户中的8人分配了重复编号,我们多次确认数组内编号均为唯一值,请问为何Firestore事务似乎允许并发编辑导致此问题?
问题原因
并非Firestore事务允许并发编辑,而是事务重试机制结合随机选号逻辑,在高并发场景下触发了重复分配。
Firestore事务的核心机制是:若事务读取文档后,该文档被其他事务修改,当前事务会自动重试整个函数逻辑。你的代码中,每次重试都会重新读取最新的储物柜数组,再生成随机索引选号。
当大量请求并发触发时,多个事务可能在同一时间窗口读取到相同的原始数组,选中同一个索引对应的储物柜编号。第一个提交成功的事务会移除该编号,后续事务提交时会检测到冲突并重试——但重试时,若随机索引再次选中某个编号,而该编号刚好被另一并发事务同时选中并提交,就会出现重复分配。尤其是当数组剩余编号越少时,这种重复概率会显著升高。
另外,代码中status字段存在拼写错误(succes应为success),但这不会直接导致重复分配问题。
修复方案
要彻底解决重复分配,需修改选号逻辑,避免随机选号带来的并发冲突:
- 固定位置取号:直接从数组头部或尾部取号(比如取第一个元素),这样每次事务处理的取号逻辑是确定的,重试时会自动取最新数组的对应位置元素,确保每个编号仅被分配一次。示例修改:
if (lockerArr.length > 0){ // 改为取数组第一个元素 var userLocker = lockerArr[0]; var newLockers = lockerArr.slice(1); t.update(lockerref, {'lockers': newLockers}); return {status: 'success', locker: userLocker}; } - 串行化处理:将储物柜分配请求放入队列,串行处理,彻底避免并发冲突,但会增加系统复杂度。
内容的提问来源于stack exchange,提问作者Walter Snijders
相关产品推荐
相关产品推荐

