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

Firestore中如何执行批量事务?一致性保障方案咨询

Firestore 事务与统计数据一致性解决方案

关于批量事务的疑问

Firestore 并非不支持批量事务,它提供两种原子性操作方式:

  • 批量写入(Batch Write):适合无需读取现有数据、直接执行多个写入/更新/删除操作的场景,所有操作原子性执行,要么全成功要么全失败。但它无法基于现有数据计算后再更新,所以不适合你的统计更新场景。
  • 事务(Transaction):支持先读取文档数据,基于读取结果计算后再执行写入/更新,整个过程同样是原子性的,完全能满足你同时处理三个操作并保证一致性的需求。

保证收支统计一致性的实现方式

你的场景需要先读取当前总统计、月度统计的数值,加上新交易的金额后再更新,必须用事务来包裹所有操作。示例代码(以JavaScript为例):

const db = firebase.firestore();

// 假设新交易数据
const newTransaction = {
  amount: 100,
  type: 'income', // 收入/支出
  date: new Date(),
  // 其他字段...
};

// 执行事务
db.runTransaction(async (transaction) => {
  // 1. 获取总统计文档
  const totalStatsRef = db.collection('stats').doc('total');
  const totalStatsDoc = await transaction.get(totalStatsRef);
  if (!totalStatsDoc.exists) {
    // 若不存在则初始化
    transaction.set(totalStatsRef, { income: 0, expense: 0 });
  }
  const currentTotal = totalStatsDoc.data() || { income: 0, expense: 0 };

  // 2. 获取月度统计文档(假设以YYYY-MM为文档ID)
  const monthKey = `${newTransaction.date.getFullYear()}-${String(newTransaction.date.getMonth() + 1).padStart(2, '0')}`;
  const monthlyStatsRef = db.collection('stats').doc(monthKey);
  const monthlyStatsDoc = await transaction.get(monthlyStatsRef);
  if (!monthlyStatsDoc.exists) {
    transaction.set(monthlyStatsRef, { income: 0, expense: 0 });
  }
  const currentMonthly = monthlyStatsDoc.data() || { income: 0, expense: 0 };

  // 3. 计算更新后的值
  const updatedTotal = {
    income: currentTotal.income + (newTransaction.type === 'income' ? newTransaction.amount : 0),
    expense: currentTotal.expense + (newTransaction.type === 'expense' ? newTransaction.amount : 0)
  };
  const updatedMonthly = {
    income: currentMonthly.income + (newTransaction.type === 'income' ? newTransaction.amount : 0),
    expense: currentMonthly.expense + (newTransaction.type === 'expense' ? newTransaction.amount : 0)
  };

  // 执行三个操作:添加交易、更新总统计、更新月度统计
  transaction.set(db.collection('transactions').doc(), newTransaction);
  transaction.update(totalStatsRef, updatedTotal);
  transaction.update(monthlyStatsRef, updatedMonthly);
})
.then(() => {
  console.log('事务执行成功');
})
.catch((error) => {
  console.error('事务失败:', error);
});

替代方案:云函数自动触发

如果不想在客户端直接处理事务,也可以用Firebase云函数监听transactions集合的新增文档事件,在云函数内部用事务更新统计数据。这种方式把逻辑放在服务端,避免客户端直接操作统计文档,同时同样依赖事务保证一致性:

// 云函数示例(Node.js)
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.updateStatsOnTransaction = functions.firestore
  .document('transactions/{transactionId}')
  .onCreate(async (snap, context) => {
    const transactionData = snap.data();
    const db = admin.firestore();

    return db.runTransaction(async (transaction) => {
      // 以下逻辑和客户端事务一致:读取总统计、月度统计,计算后更新
      const totalStatsRef = db.collection('stats').doc('total');
      const totalStatsDoc = await transaction.get(totalStatsRef);
      const currentTotal = totalStatsDoc.data() || { income: 0, expense: 0 };

      const monthKey = `${transactionData.date.toDate().getFullYear()}-${String(transactionData.date.toDate().getMonth() + 1).padStart(2, '0')}`;
      const monthlyStatsRef = db.collection('stats').doc(monthKey);
      const monthlyStatsDoc = await transaction.get(monthlyStatsRef);
      const currentMonthly = monthlyStatsDoc.data() || { income: 0, expense: 0 };

      const updatedTotal = {
        income: currentTotal.income + (transactionData.type === 'income' ? transactionData.amount : 0),
        expense: currentTotal.expense + (transactionData.type === 'expense' ? transactionData.amount : 0)
      };
      const updatedMonthly = {
        income: currentMonthly.income + (transactionData.type === 'income' ? transactionData.amount : 0),
        expense: currentMonthly.expense + (transactionData.type === 'expense' ? transactionData.amount : 0)
      };

      transaction.update(totalStatsRef, updatedTotal);
      transaction.update(monthlyStatsRef, updatedMonthly);
    });
  });

需要注意的是,云函数触发时也要用事务,避免多个并发的交易触发云函数时,统计数据出现竞态条件(比如两个交易同时读取同一个统计值,导致更新后的值不正确)。

内容的提问来源于stack exchange,提问作者Laczkó Örs

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 14:10:27