Transaction 1 has been committed in MongoDB

狂风中的少年 提交于 2021-01-05 07:26:45

问题


I am trying to use transactions to update multiple documents.

One being a loading sheet document [await sheet.save({ session });]

and other being an array of stock reservation records [await Stock.bulkWrite()].

    const session = await mongoose.startSession();

    session.startTransaction({
      readPreference: 'primary',
      readConcern: { level: 'local' },
      writeConcern: { w: 'majority' },
    });

    let sheetAfterSave: any = null;

    try {
      sheetAfterSave = await sheet.save({ session });
      records.forEach(async (el: any) => {
        let updatedStockRecord = await Stock.bulkWrite(
          [
            {
              updateOne: {
                filter: {
                  index: el.index,
                  product: el.product,
                  batchNo: el.batchNo,
                  agency,
                  totalQuantity: { $gte: el.loadingTotal },
                },
                update: {
                  $push: {
                    reservations: {
                      loadingSheetId: sheetAfterSave._id,
                      reservedCaseQuantity: el.loadingCaseCount,
                      reservedUnitQuantity: el.loadingUnitCount,
                      reservedTotalQuantity: el.loadingTotal,
                    },
                  },
                },
              },
            },
          ],
          {
            session: session,
          }
        );
      });
      await session.commitTransaction();
      session.endSession();
    } catch (error) {
      console.log(error);
      await session.abortTransaction();
      session.endSession();
      throw new Error(
        `Error occured while trying to create a new loading sheet. ${error}`
      );
    }

In this operation loading sheet document gets saved in the database, but not the stock reservation records array.

It gives the error

[distribution] MongoError: Transaction 1 has been committed.
[distribution]     at MessageStream.messageHandler (/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/connection.js:263:20)
[distribution]     at MessageStream.emit (node:events:376:20)
[distribution]     at processIncomingData (/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/message_stream.js:144:12)
[distribution]     at MessageStream._write (/app/node_modules/mongoose/node_modules/mongodb/lib/cmap/message_stream.js:42:5)
[distribution]     at writeOrBuffer (node:internal/streams/writable:388:12)
[distribution]     at MessageStream.Writable.write (node:internal/streams/writable:333:10)
[distribution]     at TLSSocket.ondata (node:internal/streams/readable:716:22)
[distribution]     at TLSSocket.emit (node:events:376:20)
[distribution]     at addChunk (node:internal/streams/readable:305:12)
[distribution]     at readableAddChunk (node:internal/streams/readable:280:9)
[distribution]     at TLSSocket.Readable.push (node:internal/streams/readable:219:10)
[distribution]     at TLSWrap.onStreamRead (node:internal/stream_base_commons:192:23)
[distribution] [ERROR] 14:56:43 MongoError: Transaction 1 has been committed.

As I use the session in a failure like this the saved documents must be rolled back but its not happening here.

Am I missing something here?? Appreciate your help

Cheers

Do you see anything wrong with this code. I am trying to create a dynamic error inside the loop to make sure that all the the transactions are rolled back if any error occurs inside the loop?? Dynamic error outside the loop rolls back all the transactions perfectly but not the one inside the loop.

const session = await mongoose.startSession();

try {
  await session.withTransaction(
    async () => {
      sheetAfterSave = await sheet.save({ session });
      records.forEach(async (el: any) => {
        let updatedStockRecord = await Stock.bulkWrite(
          [
            {
              updateOne: {
                filter: {
                  index: el.index,
                  product: el.product,
                  batchNo: el.batchNo,
                  agency,
                  totalQuantity: { $gte: el.loadingTotal },
                },
                update: {
                  $push: {
                    reservations: {
                      loadingSheetId: sheetAfterSave._id,
                      reservedCaseQuantity: el.loadingCaseCount,
                      reservedUnitQuantity: el.loadingUnitCount,
                      reservedTotalQuantity: el.loadingTotal,
                    },
                  },
                },
              },
            },
          ],
          {
            session: session,
          }
        );
        console.log('******************');
        throw new Error('12/24/2020 ERROR INSIDE LOOP'); //  MongoError: Transaction 1 has been committed.
      });
      throw new Error('12/24/2020 ERROR OUTSIDE LOOP'); // All transactions are rolled back perfectly
    },
    {
      readPreference: 'primary',
      readConcern: { level: 'local' },
      writeConcern: { w: 'majority' },
    }
  );
} catch (error) {
  console.log('ERROR BLOCK', error);
  throw new Error(
    `Error occured while trying to create a new loading sheet. ${error}`
  );
} finally {
  session.endSession();
  await mongoose.connection.close();
  console.log('************ FINALLY *****************');
}

回答1:


I was able to solve the issue.

Problem was not with the below code

  await session.commitTransaction(); (success)
  session.endSession();              (failure)
} catch (error) {                    (entered)
  await session.abortTransaction();  (invoked)

but it was with the records.forEach loop.

records.forEach(async (el: any) => {...});

inside the foreach when throwing an error it is not caught by the outermost try catch block since the content inside the loop is in a different functional context than the code outside of the loop.

Once I changed the loop from .forEach to

for (const el of records) {}

its working as expected. Posting the answer in case if someone faces the same in the future. Thanks for the support guys :)




回答2:


The following sequence of calls/events will attempt to abort a committed transaction:

      await session.commitTransaction(); (success)
      session.endSession();              (failure)
    } catch (error) {                    (entered)
      await session.abortTransaction();  (invoked)

See https://docs.mongodb.com/manual/core/transactions/ for correct usage.



来源:https://stackoverflow.com/questions/65426546/transaction-1-has-been-committed-in-mongodb

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!