为什么 insertmany 不能使用 mongoos 进行交易?

why insertmany not working using mongoos with transactions?

我正在尝试使用 inertMany 插入数据。但我无法插入数据,为什么?我正在使用 mongoose session 如果发生任何错误,我会回滚更改

https://codesandbox.io/s/dreamy-bell-9u0bz

app.get("/saveData", async (req, res, next) => {
  const session = await mongoose.startSession();
  session.startTransaction();
  try {
    const data = [
      {
        empid: "Ad",
        id: 4,
        date: "19-Jul-2019"
      },
      {
        empid: "Bc",
        id: 56,
        date: "18-Jul-2019"
      },

      {
        empid: "C",
        id: 6,
        date: "11-Jul-2019"
      }
    ];
    console.log("before save");
    let saveBlog = await BlogPostModel.insertMany(data, { session }); //when fail its goes to catch
    await session.commitTransaction();
    return res.send(saveBlog);
  } catch (error) {
    console.log(error);
    await session.abortTransaction();
    return res.status(400).send(error);
  }
});

既然你好像没看懂marked duplicate or the comment on your last question,直接演示一下:

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

const uri = 'mongodb://localhost:27017/test';
const opts = { useNewUrlParser: true, useUnifiedTopology: true };

mongoose.Promise = global.Promise;

mongoose.set('debug', true);
mongoose.set('useCreateIndex', true);
mongoose.set('useFindAndModify', false);

const blogPostSchema = new Schema({
  id: { type: Number, unique: true },
  empid: String,
  date: Date
});

const BlogPost = mongoose.model('BlogPost', blogPostSchema);

const sampleData = [
  { empid: "test13", id: 6, date: '11-Jul-2019' },
  { empid: "test123", id: 4, date: '19-Jul-2019' },
  { empid: "test13", id: 4, date: '18-Jul-2019' }
];

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {
    const conn = await mongoose.connect(uri, opts);

    // Clean data
    await Promise.all(
      Object.values(conn.models).map(m => m.deleteMany())
    );

    // Collections must existi in transactions
    await Promise.all(
      Object.values(conn.models).map(m => m.createCollection())
    );


    // With Transaction
    log("With Transaction");
    let session = await conn.startSession();
    session.startTransaction();

    try {
      await BlogPost.insertMany(sampleData, { session });
      await session.commitTransaction();
    } catch(e) {
      // Show the error and abort
      log({ err: e.errmsg, result: e.result.result.writeErrors });
      await session.abortTransaction();
    }

    log({ results: (await BlogPost.find()) });

    // No transaction
    log("Without Transaction");
    try {
      await BlogPost.insertMany(sampleData);
    } catch(e) {
      // Show the error
      log({ err: e.errmsg, result: e.result.result.writeErrors });
    }

    log({ results: (await BlogPost.find()) });


  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})();

并且输出:

Mongoose: blogposts.createIndex({ id: 1 }, { unique: true, background: true })
Mongoose: blogposts.deleteMany({}, {})
"With Transaction"
Mongoose: blogposts.insertMany([ { _id: 5d8f28ac462a1e1a8c6838a2, empid: 'test13', id: 6, date: 2019-07-10T14:00:00.000Z, __v: 0 }, { _id: 5d8f28ac462a1e1a8c6838a3, empid: 'test123', id: 4, date: 2019-07-18T14:00:00.000Z, __v: 0 }, { _id: 5d8f28ac462a1e1a8c6838a4, empid: 'test13', id: 4, date: 2019-07-17T14:00:00.000Z, __v: 0 } ], { session: ClientSession("650da06d23544ef8bc1d345d93331d1e") })
{
  "err": "E11000 duplicate key error collection: test.blogposts index: id_1 dup key: { id: 4 }",
  "result": [
    {
      "code": 11000,
      "index": 2,
      "errmsg": "E11000 duplicate key error collection: test.blogposts index: id_1 dup key: { id: 4 }",
      "op": {
        "_id": "5d8f28ac462a1e1a8c6838a4",
        "empid": "test13",
        "id": 4,
        "date": "2019-07-17T14:00:00.000Z",
        "__v": 0
      }
    }
  ]
}
Mongoose: blogposts.find({}, { projection: {} })
{
  "results": []
}
"Without Transaction"
Mongoose: blogposts.insertMany([ { _id: 5d8f28ac462a1e1a8c6838a5, empid: 'test13', id: 6, date: 2019-07-10T14:00:00.000Z, __v: 0 }, { _id: 5d8f28ac462a1e1a8c6838a6, empid: 'test123', id: 4, date: 2019-07-18T14:00:00.000Z, __v: 0 }, { _id: 5d8f28ac462a1e1a8c6838a7, empid: 'test13', id: 4, date: 2019-07-17T14:00:00.000Z, __v: 0 } ], {})
{
  "err": "E11000 duplicate key error collection: test.blogposts index: id_1 dup key: { id: 4 }",
  "result": [
    {
      "code": 11000,
      "index": 2,
      "errmsg": "E11000 duplicate key error collection: test.blogposts index: id_1 dup key: { id: 4 }",
      "op": {
        "_id": "5d8f28ac462a1e1a8c6838a7",
        "empid": "test13",
        "id": 4,
        "date": "2019-07-17T14:00:00.000Z",
        "__v": 0
      }
    }
  ]
}
Mongoose: blogposts.find({}, { projection: {} })
{
  "results": [
    {
      "_id": "5d8f28ac462a1e1a8c6838a5",
      "empid": "test13",
      "id": 6,
      "date": "2019-07-10T14:00:00.000Z",
      "__v": 0
    },
    {
      "_id": "5d8f28ac462a1e1a8c6838a6",
      "empid": "test123",
      "id": 4,
      "date": "2019-07-18T14:00:00.000Z",
      "__v": 0
    }
  ]
}

请注意,在使用事务时,没有任何项目插入到集合中。使用具有默认行为 ordered: trueinsertMany() 将插入所有 批处理 项目,直到遇到任何错误。

另请注意,因为您确实期待 错误,所以您 必须 在其中包含这样的声明 try..catch 或类似的错误处理程序。否则任何错误(在示例情况下是预期的)将简单地落入 outer catch,当然在演示中它只是退出程序。


实际上不在问题本身,但在 How to use MongoDB transaction using Mongoose? 的演示中实际上没有提到的东西确实是你应该知道 whlist 交易是活动的必须 还必须在 任何 后续读取中包含 session 属性,以便查看在该事务中所做的更改。

例如,以下内容不会显示集合中的任何内容:

let session = await conn.startSession();
session.startTransaction();

try {
  await BlogPost.insertMany(sampleData, { session });
  let documents = await BlogPost.find(); // This would return nothing
  await session.commitTransaction();
} catch(e) {
  // Show the error and abort
  log({ err: e.errmsg, result: e.result.result.writeErrors });
  await session.abortTransaction();
}

然而,在 find() 中包含 session 将实际显示插入的内容:

try {
  await BlogPost.insertMany(sampleData, { session });
  // Actually includes the session and therefore the state
  let documents = await BlogPost.find({},{ session });

  await session.commitTransaction();
} catch(e) {
  // Show the error and abort
  log({ err: e.errmsg, result: e.result.result.writeErrors });
  await session.abortTransaction();
}

当然,在这种情况下,read 将取决于 insertMany() not 因任何原因而失败,因为任何错误都会导致在发出下一个请求之前退出到 catch

一旦事务被提交,它当然可用于连接的全局状态。但是,虽然 正在进行 只有包含与交易开始时相同的 session 信息的操作才能看到该交易中实施的任何更改。

对于在 inserMany 中将会话作为选项传递时出现错误“无法读取未定义的 属性 'map'”的人,出现此错误是因为您的 mongo 是 运行作为独立服务器,要解决这个问题可以参考 npm package run-rs 或按照这个答案来解决这个问题: