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

无法删除Firestore特定文档:疑似事务失败致死锁求助

Firestore特定文档无法删除、字段自动恢复问题排查与解决

问题现象

  • 文档路径:mohallas/Ax7nRPPighhHXoO4THIRxmVjsAk2 无法通过Admin SDK或Firestore UI删除,UI操作持续处于加载状态
  • 可删除该文档字段,但字段会自动恢复,疑似处于死锁状态;清空字段后,Client SDK仍会保留文档的旧副本
  • 该文档此前经历过失败的事务状态,对应Cloud Function日志显示事务重试超时:
    日志截图1
    日志截图2

问题根源分析

从提供的Cloud Function代码来看,核心问题出在事务逻辑和事件处理机制上:

  1. 事务无限重试循环:代码为事务设置了maxAttempts:30,当目标文档处于异常状态时,事务会持续重试更新该文档,直接覆盖删除操作,导致字段自动恢复。
  2. 事件去重逻辑漏洞:虽然用getFunctionEvent和memoizeFunctionEvent做事件去重,但如果存储去重标记的机制失效,会导致同一事件被重复处理,反复触发对目标文档的更新。
  3. 事务拆分风险:文档更新场景中(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 20:40:48