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

MongoDB避免更新时脏读及Node+Mongoose钱包API数据一致性方案

Node + Mongoose 钱包API数据一致性解决方案

1. 读取Wallet文档时余额被更新,确保获取最新值

避免复用Mongoose文档缓存

Mongoose的Document实例会缓存查询结果,若后续文档被其他请求更新,旧实例仍保留旧值。每次需要最新数据时,直接重新执行查询,不要复用之前的Document对象:

// 错误:复用旧实例导致读取旧值
const oldWallet = await Wallet.findById(walletId);
// 其他请求更新余额后,oldWallet仍为旧数据

// 正确:每次查询获取最新数据,用lean()返回普通对象提升性能
const getLatestWallet = async (walletId) => {
  return await Wallet.findById(walletId).lean();
};

原子更新+返回最新值

如果是更新操作后需要最新余额,直接用findOneAndUpdate并设置new: true,一步完成原子更新和最新数据读取,避免先读后写的竞态问题:

// 原子扣减余额并返回更新后的钱包数据
const updatedWallet = await Wallet.findOneAndUpdate(
  { _id: walletId },
  { $inc: { balance: -amount } }, // MongoDB原子操作,避免并发冲突
  { new: true, runValidators: true }
);

事务确保读写一致性

针对先读再写的复杂操作(如余额校验后扣减),使用Mongoose事务将查询和更新绑定在同一个会话中,确保读取的是最新数据:

const session = await mongoose.startSession();
session.startTransaction();

try {
  // 事务内查询,保证数据未被其他会话修改
  const wallet = await Wallet.findById(walletId).session(session);
  if (!wallet) throw new Error('钱包不存在');
  
  // 执行业务逻辑(如余额校验)
  if (wallet.balance < amount) throw new Error('余额不足');
  
  wallet.balance -= amount;
  await wallet.save({ session });
  
  await session.commitTransaction();
} catch (err) {
  await session.abortTransaction();
  throw err;
} finally {
  session.endSession();
}

2. 读取文档时钱包被删除,确保获取正确信息

强制检查查询结果

无论使用哪种查询方式,都必须判断返回结果是否为null,以此确认钱包是否已被删除:

const wallet = await Wallet.findById(walletId);
if (!wallet) {
  throw new Error('钱包已删除或不存在');
}
// 后续业务逻辑基于存在的钱包执行

乐观锁防止过期操作

给Wallet Schema添加version字段,每次更新时递增版本号,查询或更新时校验版本号,避免对已删除/修改的旧数据进行操作:

// 定义Schema时添加版本字段
const walletSchema = new mongoose.Schema({
  balance: { type: Number, default: 0 },
  version: { type: Number, default: 0 }
});

// 更新时校验版本号,确保数据未被篡改或删除
const updatedWallet = await Wallet.findOneAndUpdate(
  { _id: walletId, version: currentVersion },
  { $inc: { balance: -amount, version: 1 } },
  { new: true }
);

if (!updatedWallet) {
  throw new Error('钱包数据已过期或已删除');
}

事务内的存在性校验

在事务中先查询钱包是否存在,再执行后续操作,利用数据库事务的隔离性,避免查询后钱包被删除的情况:

const session = await mongoose.startSession();
session.startTransaction();

try {
  const wallet = await Wallet.findById(walletId).session(session);
  if (!wallet) {
    throw new Error('钱包已删除');
  }
  
  // 执行业务操作
  await wallet.save({ session });
  
  await session.commitTransaction();
} catch (err) {
  await session.abortTransaction();
  throw err;
} finally {
  session.endSession();
}

内容的提问来源于stack exchange,提问作者ritwick_ _D

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 15:11:23