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

MongoDB事务未回滚及重复执行问题排查(附Mongoose代码)

Mongoose事务实现问题排查

遇到的问题

  • 事务中任一操作失败时,未正确执行回滚;
  • 事务回滚后,1分钟后自动启动新事务重复执行相同操作,触发错误:[Symbol(errorLabels)]: Set(1) { 'TransientTransactionError' }

原始代码

var mongo = require("mongoose")
session10 = await mongo.startSession
 try {
        
        await session10.withTransaction(async () => {
            console.log("transaction started");
        await readonlymodel.findOneAndUpdate({}, {
            $set: {
                readonlyMode: true,
                yearEndClosure: true
            }
        }, { session:session10 });
        var archivalActivity = {
            activity_id: 4,
            activityName: 'Current year Data Back',
            activity_Status: 'In Progress',
            err_occured: 'None',
            remarks: 'None'
        }
        await year_end_closure_model.findOneAndUpdate({ year: MCpayload.year, month: MCpayload.month, 'activities.activity_id': 3 }, {
            $set: {
                'activities.$.activity_Status': 'Completed',
            },
    
        }, { session:session10 });
        await year_end_closure_model.findOneAndUpdate({ year: MCpayload.year, month: MCpayload.month }, {
            $push: {
                activities: archivalActivity
            }
        }, {session: session10 })
 const dataCurrentYear = await model.find({}, { _id: 0 }, {session: session10 })
        const etBackupData = await archival_yearEnd.insertMany(etdataCurrentYear, {session: session10 });
        console.log(dataCurrentYear.length, etBackupData.length, "inserted Succesfully")
        const etproofCurrentYear = await proofmodel.find({}, { _id: 0 }, { session: session10 });
        const proofBackupData = await proof_archival_yearEnd.insertMany(proofCurrentYear, { session:session10 });
        console.log(proofCurrentYear.length, proofBackupData.length, "inserted Succesfully")
        const proofCurrentYear = await commonproofmodel.find({}, { _id: 0 }, {session: session10 });
        const proofBackupData = await common_proof_archival_yearEnd.insertMany(proofCurrentYear, { session:session10 });
        console.log(cmnproofCurrentYear.length, cmnproofBackupData.length, "inserted Succesfully")
        const etcaseCurrentYear = await etcasemodel.find({}, { _id: 0 }, {session: session10 });
        const etcaseBackupData = await caseStudy_proof_archival_yearEnd.insertMany(etcaseCurrentYear, {session: session10 });
        console.log(etcaseCurrentYear.length, etcaseBackupData.length, "inserted Succesfully")
        const SummaryCurrentYear = await summary_model.find({}, { _id: 0 }, {session: session10 });
        const SummaryBackupData = await Summary_archive_yearEnd.insertMany(SummaryCurrentYear, {session: session10 });
        console.log(SummaryCurrentYear.length, SummaryBackupData.length, "inserted Succesfully")
        const DetailsCurrentYear = await data_model.find({}, { _id: 0 }, { session:session10 });
        const DetailsBackupData = await Detail_archive_yearEnd.insertMany(DetailsCurrentYear, {session: session10 });
        console.log(DetailsCurrentYear.length, DetailsBackupData.length, "inserted Succesfully")
        const ExpCurrentYear = await umt_exception_model.find({}, { _id: 0 }, {session:session10 });
        const ExpBackupData = await UmtExceptional_archive_yearEnd.insertMany(ExpCurrentYear, {session: session10 });
        console.log(ExpCurrentYear.length, ExpBackupData.length, "inserted Succesfully")
    

more operations to do//
return true
}catch(err){
console.log(err)
}finally{
session10.endSession()
}

错误分析与修正点

1. 会话启动错误

session10 = await mongo.startSession 缺少调用括号,正确写法应为 session10 = await mongo.startSession()。原代码获取的是函数引用而非会话实例,导致事务无法正确初始化。

2. 事务异常处理逻辑错误

withTransaction 的回调内部添加了catch块,会吃掉操作异常,导致Mongoose无法感知错误触发回滚。正确做法是移除内部catch,让Mongoose的事务机制自动处理异常与回滚;若必须内部捕获,需重新抛出异常(throw err)。

3. 变量名错误

代码中存在多处变量名不一致问题,比如 insertMany(etdataCurrentYear) 中的etdataCurrentYear应为dataCurrentYear,这类错误会直接导致操作失败,若被内部catch吃掉则事务无法回滚。

4. 会话关闭时机错误

session10.endSession() 放在了withTransaction的内部finally块,会提前终止会话,干扰事务流程。应将会话关闭逻辑移到外层finally块,确保事务完成后再关闭会话。

5. TransientTransactionError 触发原因

该错误源于事务重试机制:Mongoose默认会自动重试事务,加上原代码会话管理错误、批处理任务未正确处理失败状态,导致事务失败后重复执行。可通过withTransaction的第二个参数配置重试策略,同时确保批处理任务在事务失败后不再重复触发。

修正后的代码示例

const mongoose = require("mongoose");

async function runYearEndTransaction(MCpayload) {
  let session;
  try {
    // 正确启动会话
    session = await mongoose.startSession();
    
    // 移除内部catch,让Mongoose处理事务异常
    const transactionResult = await session.withTransaction(async () => {
      console.log("transaction started");
      
      await readonlymodel.findOneAndUpdate({}, {
        $set: { readonlyMode: true, yearEndClosure: true }
      }, { session });

      const archivalActivity = {
        activity_id: 4,
        activityName: 'Current year Data Back',
        activity_Status: 'In Progress',
        err_occured: 'None',
        remarks: 'None'
      };

      await year_end_closure_model.findOneAndUpdate(
        { year: MCpayload.year, month: MCpayload.month, 'activities.activity_id': 3 },
        { $set: { 'activities.$.activity_Status': 'Completed' } },
        { session }
      );

      await year_end_closure_model.findOneAndUpdate(
        { year: MCpayload.year, month: MCpayload.month },
        { $push: { activities: archivalActivity } },
        { session }
      );

      // 修正变量名错误
      const dataCurrentYear = await model.find({}, { _id: 0 }, { session });
      const etBackupData = await archival_yearEnd.insertMany(dataCurrentYear, { session });
      console.log(dataCurrentYear.length, etBackupData.length, "inserted Succesfully");

      const etproofCurrentYear = await proofmodel.find({}, { _id: 0 }, { session });
      const proofBackupData = await proof_archival_yearEnd.insertMany(etproofCurrentYear, { session });
      console.log(etproofCurrentYear.length, proofBackupData.length, "inserted Succesfully");

      const cmnproofCurrentYear = await commonproofmodel.find({}, { _id: 0 }, { session });
      const cmnProofBackupData = await common_proof_archival_yearEnd.insertMany(cmnproofCurrentYear, { session });
      console.log(cmnproofCurrentYear.length, cmnProofBackupData.length, "inserted Succesfully");

      // 其他操作同理修正变量名
      const etcaseCurrentYear = await etcasemodel.find({}, { _id: 0 }, { session });
      const etcaseBackupData = await caseStudy_proof_archival_yearEnd.insertMany(etcaseCurrentYear, { session });
      console.log(etcaseCurrentYear.length, etcaseBackupData.length, "inserted Succesfully");

      const SummaryCurrentYear = await summary_model.find({}, { _id: 0 }, { session });
      const SummaryBackupData = await Summary_archive_yearEnd.insertMany(SummaryCurrentYear, { session });
      console.log(SummaryCurrentYear.length, SummaryBackupData.length, "inserted Succesfully");

      const DetailsCurrentYear = await data_model.find({}, { _id: 0 }, { session });
      const DetailsBackupData = await Detail_archive_yearEnd.insertMany(DetailsCurrentYear, { session });
      console.log(DetailsCurrentYear.length, DetailsBackupData.length, "inserted Succesfully");

      const ExpCurrentYear = await umt_exception_model.find({}, { _id: 0 }, { session });
      const ExpBackupData = await UmtExceptional_archive_yearEnd.insertMany(ExpCurrentYear, { session });
      console.log(ExpCurrentYear.length, ExpBackupData.length, "inserted Succesfully");

      // 更多操作...

      return true;
    });

    if (!transactionResult) {
      console.log("事务未提交,已回滚");
    }
  } catch (err) {
    console.error("事务执行出错:", err);
  } finally {
    // 确保会话正确关闭
    if (session) {
      await session.endSession();
    }
  }
}

内容的提问来源于stack exchange,提问作者匿名用户

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:24:51