注册

mongoDB 4.0事务回滚的辛酸历程探究

下面我们来详细讲解“mongoDB 4.0事务回滚的辛酸历程探究”的完整攻略。

概述

在mongoDB 4.0版本中,引入了对事务的支持。这个功能对于一些复杂的应用场景来说非常重要。但是,在使用事务的过程中,用户可能会遇到一些意想不到的问题,比如事务回滚失败等。本文将详细介绍使用mongoDB 4.0事务时的注意事项和陷阱,并结合两个实例来说明。

示例一

首先,我们来看一个简单的例子:

async function runTxn() {
  let session = client.startSession({defaultTransactionOptions: {readConcern: {level: 'local'}}});
  session.startTransaction();
  try {
    const opts = { session };
    const result1 = await db.collection('collection1').insertOne({ a: 1 }, opts);
    const result2 = await db.collection('collection2').updateOne({ a: 1 }, { $set: { b: 2 } }, opts);
    throw new Error('Exception');
    await session.commitTransaction();
  } catch (err) {
    await session.abortTransaction();
    console.log('Aborted');
  } finally {
    session.endSession();
  }
}

这个例子创建了两个集合collection1collection2,然后使用事务在它们中插入和更新一些数据再抛出一个异常。然后,该事务应该回滚并输出“Aborted”字符串。

但是,在mongoDB 4.0正式发布后,有一些用户遇到了这样的问题,即“Aborted”字符串没有被输出,而是得到了一个事务已经提交的消息。

这个问题的原因是用户在抛出异常后没有立即终止事务,导致事务虽然抛出了异常但仍被提交。

在这个例子中,我们可以通过在事务代码块的最后添加throw err;的语句,来修复这个问题。

async function runTxn() {
  let session = client.startSession({defaultTransactionOptions: {readConcern: {level: 'local'}}});
  session.startTransaction();
  try {
    const opts = { session };
    const result1 = await db.collection('collection1').insertOne({ a: 1 }, opts);
    const result2 = await db.collection('collection2').updateOne({ a: 1 }, { $set: { b: 2 } }, opts);
    throw new Error('Exception');
    await session.commitTransaction();
  } catch (err) {
    await session.abortTransaction();
    console.log('Aborted');
    throw err;
  } finally {
    session.endSession();
  }
}

示例二

接下来,让我们看看另一个例子:

async function runTxn() {
  let session = client.startSession({defaultTransactionOptions: {readConcern: {level: 'local'}}});
  session.startTransaction();
  try {
    const opts = { session };
    const result1 = await db.collection('collection1').insertOne({ a: 1 }, opts);
    const result2 = await db.collection('collection2').updateOne({ a: 1 }, { $set: { b: 2 } }, opts);
    const result3 = await db.collection('collection3').insertOne({ b: 3 }, opts);
    await session.commitTransaction();
  } catch (err) {
    await session.abortTransaction();
    console.log('Aborted');
  } finally {
    session.endSession();
  }
}

这个例子与前一个例子类似,不同之处在于我们向另一个集合collection3插入了一些数据。

如果在这个例子中发生了一个故障,比如是机器宕机,我们再次启动mongoDB时,会收到“Prepare transaction error”的错误消息。

这是因为我们没有清理事务状态。在我们的代码中,我们没有在catch块中设置或删除Mongo.Collection状态。如果我们在catch块中添加以下行:

MongoInternals.NpmModules.mongodb_client_session.modernized((clientSession) => {
  clientSession && clientSession.abortTransaction();
});

可以解决这个问题并正确地回滚事务。

总结

在使用mongoDB 4.0事务时,我们需要注意一些细节和问题。在示例一中,我们需要正确处理异常以确保事务正确回滚。在示例二中,我们需要正确处理Mongo.Collection状态以确保在发生意外故障时事务正确回滚。