无法删除Firestore特定文档:疑似事务失败致死锁求助
Firestore特定文档无法删除、字段自动恢复问题排查与解决
问题现象
- 文档路径:
mohallas/Ax7nRPPighhHXoO4THIRxmVjsAk2无法通过Admin SDK或Firestore UI删除,UI操作持续处于加载状态 - 可删除该文档字段,但字段会自动恢复,疑似处于死锁状态;清空字段后,Client SDK仍会保留文档的旧副本
- 该文档此前经历过失败的事务状态,对应Cloud Function日志显示事务重试超时:


问题根源分析
从提供的Cloud Function代码来看,核心问题出在事务逻辑和事件处理机制上:
- 事务无限重试循环:代码为事务设置了
maxAttempts:30,当目标文档处于异常状态时,事务会持续重试更新该文档,直接覆盖删除操作,导致字段自动恢复。 - 事件去重逻辑漏洞:虽然用
getFunctionEvent和memoizeFunctionEvent做事件去重,但如果存储去重标记的机制失效,会导致同一事件被重复处理,反复触发对目标文档的更新。 - 事务拆分风险:文档更新场景中(before和after都存在),代码将计数增减拆分为两个独立事务,既失去了操作原子性,又增加了事务冲突和重试概率,进一步加剧文档异常。
解决方案步骤
1. 临时停止关联Cloud Function
先暂停inRoomUsersCounter函数,避免它继续对目标文档执行更新操作,阻止字段自动恢复的循环。
2. 强制删除异常文档
使用Firebase Admin SDK执行强制删除,确保操作不受触发器干扰:
const admin = require('firebase-admin'); admin.initializeApp(); async function forceDeleteTargetDoc() { try { await admin.firestore().doc('mohallas/Ax7nRPPighhHXoO4THIRxmVjsAk2').delete(); console.log('异常文档删除成功'); } catch (error) { console.error('删除失败:', error); } } forceDeleteTargetDoc();
执行前务必确认Cloud Function已停止,避免删除操作被事务覆盖。
3. 修复Cloud Function代码问题
对原函数逻辑进行优化,解决事务和事件去重的核心问题:
- 合并拆分的事务,保证操作原子性
- 降低事务重试次数,避免无限制循环
- 完善异常捕获,防止失败事件重复处理
优化后的代码示例:
exports.inRoomUsersCounter = functions.runWith({ timeoutSeconds: 540 }).firestore.document('/in_room_users/{doc}').onWrite(async (change, context) => { const mohallas = "mohallas"; const homes = "rooms"; const eventId = context.eventId; // 跳过无意义的字段更新(仅mohalla和home不变时) if (change.before.exists && change.after.exists) { const beforeMohalla = change.before.get('mohalla'); const afterMohalla = change.after.get('mohalla'); const beforeHome = change.before.get('home'); const afterHome = change.after.get('home'); if (beforeMohalla === afterMohalla && beforeHome === afterHome) { return null; } } // 事件去重:确保同一事件仅处理一次 const eventDoc = await getFunctionEvent(eventId); if (eventDoc.exists()) { return null; } await memoizeFunctionEvent(eventId); try { if (change.before.exists && change.after.exists) { // 合并增减计数为单个事务,保证原子性 await admin.firestore().runTransaction(async (t) => { const beforeData = change.before.data(); const afterData = change.after.data(); // 旧数据计数递减 t.update(admin.firestore().collection(mohallas).doc(beforeData.mohalla), { 'count': admin.firestore.FieldValue.increment(-1) }); t.update(admin.firestore().collection(mohallas).doc(beforeData.mohalla).collection(homes).doc(beforeData.home), { 'count': admin.firestore.FieldValue.increment(-1) }); // 新数据计数递增 t.update(admin.firestore().collection(mohallas).doc(afterData.mohalla), { 'count': admin.firestore.FieldValue.increment(1) }); t.update(admin.firestore().collection(mohallas).doc(afterData.mohalla).collection(homes).doc(afterData.home), { 'count': admin.firestore.FieldValue.increment(1) }); }, { maxAttempts: 10 }); // 减少重试次数,避免无限循环 } else if (!change.before.exists && change.after.exists) { const afterData = change.after.data(); await admin.firestore().runTransaction(async (t) => { t.update(admin.firestore().collection(mohallas).doc(afterData.mohalla), { 'count': admin.firestore.FieldValue.increment(1) }); t.update(admin.firestore().collection(mohallas).doc(afterData.mohalla).collection(homes).doc(afterData.home), { 'count': admin.firestore.FieldValue.increment(1) }); }, { maxAttempts: 10 }); } else if (change.before.exists && !change.after.exists) { const beforeData = change.before.data(); await admin.firestore().runTransaction(async (t) => { t.update(admin.firestore().collection(mohallas).doc(beforeData.mohalla), { 'count': admin.firestore.FieldValue.increment(-1) }); t.update(admin.firestore().collection(mohallas).doc(beforeData.mohalla).collection(homes).doc(beforeData.home), { 'count': admin.firestore.FieldValue.increment(-1) }); }, { maxAttempts: 10 }); } } catch (error) { console.error('事务处理失败:', error); // 记录失败事件,避免重复触发 await memoizeFunctionEvent(eventId); throw error; } return null; });
4. 验证修复效果
重新部署优化后的Cloud Function,测试文档的创建、更新、删除流程,确认计数更新正常,不会再出现文档无法删除、字段自动恢复的问题。
内容的提问来源于stack exchange,提问作者Sid jha
相关产品推荐
相关产品推荐

