Mongo DB 4.0事务在Mongoose和NodeJs,Express中的应用

42

我正在开发一个应用程序,在其中使用了MongoDB作为数据库,并在应用层中使用Nodejs + Express,我有两个集合,分别为

  1. 用户
  2. 交易

我需要更新成千上万名用户的钱包,如果成功,则为每个交易创建一个新文档以记录相关信息。这是我的代码:

 userModel.update({_id : ObjectId(userId)}, {$inc : {wallet : 500}}, function (err, creditInfo) {
    if(err){
        console.log(err);                            
    }
    if(creditInfo.nModified > 0) {
        newTransModel = new transModel({
            usersId: ObjectId(userId),            
            amount: winAmt,         
            type: 'credit',           
        }); 
        newTransModel.save(function (err, doc) {
            if(err){
                Cb(err); 
            }
        });
    }                            
});

但是这个解决方案不是原子的(atomic),用户钱包被更新了,但相关的交易并没有在交易集合中创建,导致财务损失的可能性始终存在。

我听说最近MongoDB在其4.0版本中添加了Transactions支持,我已经阅读了MongoDB的文档,但无法成功地在Node.js中使用mongoose实现它,有人可以告诉我如何使用MongoDB的最新Transactions功能重新实现上述代码吗?

Session.startTransaction()
Session.abortTransaction()
Session.commitTransaction()

MongoDB文档:点击此处

1个回答

57

在Node.js中使用mongoose,有谁能告诉我如何使用最新的事务特性重新实现上面的代码?

要在mongoose中使用MongoDB多文档事务支持,您需要版本大于v5.2。例如:

npm install mongoose@5.2

Mongoose事务方法返回的是一个promise而不是一个会话,需要使用await。请参见:

例如,修改上述资源和您的示例,您可以尝试:

const User = mongoose.model('Users', new mongoose.Schema({
  userId: String, wallet: Number
}));
const Transaction = mongoose.model('Transactions', new mongoose.Schema({
  userId: ObjectId, amount: Number, type: String
}));

await updateWallet(userId, 500);

async function updateWallet(userId, amount) {
  const session = await User.startSession();
  session.startTransaction();
  try {
    const opts = { session };
    const A = await User.findOneAndUpdate(
                    { _id: userId }, { $inc: { wallet: amount } }, opts);

    const B = await Transaction(
                    { usersId: userId, amount: amount, type: "credit" })
                    .save(opts);

    await session.commitTransaction();
    session.endSession();
    return true;
  } catch (error) {
    // If an error occurred, abort the whole transaction and
    // undo any changes that might have happened
    await session.abortTransaction();
    session.endSession();
    throw error; 
  }
}

由于不是原子操作,用户钱包更新金额但相关交易没有在交易集合中创建的可能性始终存在,导致财务损失。

您还应考虑更改MongoDB数据模型,特别是如果这两个集合自然关联。有关更多信息,请参见Model data for Atomic Operations

一个示例模型,您可以尝试使用事件溯源模型。首先将交易条目作为事件创建,然后使用聚合重新计算用户的钱包余额。

例如:

{tranId: 1001, fromUser:800, toUser:99, amount:300, time: Date(..)}
{tranId: 1002, fromUser:77, toUser:99, amount:100, time: Date(..)}

然后,根据需求(即每6个小时)将一种计算每个用户每个周期的金额作为缓存引入。您可以通过添加以下内容来显示当前用户的钱包余额:

  • 用户的最后一个缓存金额
  • 自上次缓存金额以来发生的任何用户交易。即0-6小时前。

1
关于这个问题有两件事情: 1)await updateWallet(userId, 500); <<< 这会报错,说 await 必须在异步函数中使用,不允许在顶层使用 await。 2)由于我正在循环异步更新多个用户的钱包,所以我遇到了交易写入冲突错误。即使在 try catch 后使用 finally 块也无法解决这个问题。 - Gaurav Kumar
2
这只是一个例子,你需要根据自己的用例进行调整。1)确保按照上面所示创建异步函数。2)您可以尝试在事务中执行bulkWrite() - Wan B.
1
只需编辑您的答案并将 "User.db.startSession();" 更改为 "Mymodel.startSession()"......它说 db 未定义,我们可以使用任何 mongoose 模型来启动会话,它不仅限于该模型一旦创建。 - Gaurav Kumar
1
@GauravKumar,那个评论应该发给Kannan T对吧? - Wan B.
1
如果使用 await Promise.all 并行执行这两个查询,它们仍然能正常工作吗? - Anthony Yershov
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接