Mongoose 中的事务处理:保证多个操作的原子性

前言

在开发中,经常需要进行多个操作,例如关联的数据更新、统计信息的计算等。当这些操作需要保证原子性时,需要使用事务处理,即一组多个操作要么全部成功,要么全部失败。Mongoose 是 Node.js 中操作 MongoDB 数据库的框架,提供了方便的事务处理方式,下面将介绍 Mongoose 中的事务处理。

Mongoose 中的事务处理

Mongoose 从 5.0 版本开始支持事务处理。具体实现使用 MongoDB 4.0 引入的事务处理。Mongoose 中使用事务处理的一个关键概念是会话(Session)。会话是在 Mongoose 和 MongoDB 之间的桥梁,它允许我们跨多个操作共享事务上下文。

Mongoose 中通过在 Schema 中使用 transaction: true 来开启事务处理。在事务中,我们尝试执行一组操作,并在这些操作之间维护原子性。如果这些操作中的任何一个失败,所有操作都会回滚,也就是之前执行过的所有操作都会撤销。最终结果是,所有事务内的操作要么都成功,要么都失败。

关于事务的概念可以看阮一峰老师的博客:数据库事务教程

下面看一下 Mongoose 中的事务处理详细说明。

1. 开始事务

使用 Mongoose 进行事务处理,首先需要开启会话(Session)。我们可以通过调用 mongoose.startSession() 创建一个会话。如下所示:

const session = await mongoose.startSession();

创建完成后,我们可以在此会话上执行各种操作,这些操作可以共享事务上下文。接下来,我们调用 session.startTransaction() 开始事务。如下所示:

await session.startTransaction();

这样,我们就成功开启了一个 Mongoose 事务。

2. 向事务中添加操作

现在,我们已经成功开启了一个 Mongoose 事务,下面可以向其中添加一些操作了。在事务中执行任何操作都如同普通操作一样,只是在执行时需要传入会话对象。以更新一个用户数据为例,如下所示:

try {
  await userModel.findOneAndUpdate(
    { _id: userId },
    { $set: { name: newName } },
    { session }
  ).exec();
} catch (error) {
  console.log(`Transaction failed! Error: ${error.message}`);
}

为了在事务中执行操作,我们需要将会话对象作为 options 参数传递给操作(即上例中的 session)。

这些操作的类型可以是任何类型,包括插入、更新、删除等。

3. 发起提交或回滚

在事务完成时,我们需要提交或回滚事务。提交意味着所有操作都成功完成,而回滚意味着所有操作都失败了。Mongoose 中,分别使用 session.commitTransaction()session.abortTransaction() 发起提交或回滚。

至此,我们已经完成了 Mongoose 中的事务处理,具体实现代码可以参考下面的示例。

示例代码

下面是一个简单的示例代码,展示了如何使用 Mongoose 进行事务处理。

const { connection, Schema } = require('mongoose');

(async () => {
  // 设置 MongoDB 连接
  await connection.openUri('mongodb://localhost:27017/test');

  // 定义数据模型
  const UserSchema = new Schema({
    name: String,
    age: Number
  }, {
    transaction: true // 开启事务
  });
  const User = connection.model('User', UserSchema);

  // 开启事务
  const session = await connection.startSession();
  await session.startTransaction();

  try {
    // 添加一个用户
    const newUser = new User({ name: 'Alice', age: 26 });
    await newUser.save({ session });

    // 更新用户信息
    await User.updateOne(
      { _id: newUser._id },
      { $set: { age: 27 } },
      { session }
    );

    // 提交事务
    await session.commitTransaction();
    console.log('Transaction committed!');
  } catch (error) {
    // 回滚事务
    await session.abortTransaction();
    console.log(`Transaction failed! Error: ${error.message}`);
  } finally {
    // 关闭会话
    session.endSession();
  }

  // 关闭 MongoDB 连接
  await connection.close();
})();

注意,由于 MongoDB 不支持跨两个或更多分片的事务,所以必须在同一分片上执行事务。如果要在多个分片之间执行事务,则必须将分片集合到副本集或分片集群中。

总结

Mongoose 从 5.0 版本开始支持事务处理。事务中的操作可以保证原子性,也可以统一提交或回滚操作。使用起来非常方便,它的应用范围广泛。在多个操作之间确保原子性时,事务处理非常有用。希望本篇文章对您有所启示。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6596839beb4cecbf2da53704


纠错反馈