mongodb 事务的使用及注意事项

会话 Session

Session 是 MongoDB 3.6 之后引入的概念,在以前的版本中,Mongod 进程中的每一个请求会创建一个上下文(OperationContext),可以理解为一个单行事务,这个单行事务中对于数据、索引、oplog 的修改都是原子性的

MongoDB 3.6 之后的 Session 本质上也是一个上下文,在这个 Session 会话中多个请求共享一个上下文,为多文档事务实现提供了基础。

事务函数

  • startTransaction()

    开启一个新的事务,之后即可进行 CRUD 操作。

  • commitTransaction()

    提交事务保存数据,在提交之前事务中的变更的数据对外是不可见的。

  • abortTransaction()

    事务回滚,例如,一部分数据更新失败,对已修改过的数据也进行回滚。

  • endSession()

    结束本次会话。

Nodejs 中事务实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const db = require('./db');

const testTransaction = async (goodId) => {
const client = await db.dbInstance();
const transactionOptions = {
readConcern: { level: 'majority' },
writeConcern: { w: 'majority' },
readPreference: 'primary',
};

const session = client.startSession();
console.log('事务状态:', session.transaction.state);

try {
session.startTransaction(transactionOptions);
console.log('事务状态:', session.transaction.state);

const goodsColl = await client.db('test').collection('goods');
const orderGoodsColl = await client.db('test').collection('order_goods');
const { stock, price } = await goodsColl.findOne({ goodId }, { session });

console.log('事务状态:', session.transaction.state);

if (stock <= 0) {
throw new Error('库存不足');
}

await goodsColl.updateOne({ goodId }, {
$inc: { stock: -1 } // 库存减 1
})
await orderGoodsColl.insertOne({ id: Math.floor(Math.random() * 1000), goodId, price }, { session });
await session.commitTransaction();
} catch(err) {
console.log(`[MongoDB transaction] ERROR: ${err}`);
await session.abortTransaction();
} finally {
await session.endSession();
console.log('事务状态:', session.transaction.state);
}
}

testTransaction('g1000')

运行测试结果:

1
2
3
4
5
node index
事务状态: NO_TRANSACTION
事务状态: STARTING_TRANSACTION
事务状态: TRANSACTION_IN_PROGRESS
事务状态: TRANSACTION_COMMITTED

mogoose 中支持事务的方法

说明 方法名 备注
新建文档 create(docs,options) 一定要传入数组,使用它的重载方法

注意事项

  1. 在事务中,只能使用事务语句进行操作,不能与普通语句混用