基本使用场景
- 多文档操作一致性:当需要对多个文档进行原子性操作时,比如在一个电商系统中,同时更新订单文档和库存文档,确保订单创建成功时库存相应减少,若其中一个操作失败,整个操作回滚,保证数据一致性。
- 跨集合操作:在复杂业务场景下,需要对不同集合的文档进行关联操作并保证一致性,如用户注册时,同时在“users”集合插入用户信息,在“profiles”集合初始化用户配置信息。
操作步骤
- 开启事务:
在 MongoDB 4.0 及以上版本,使用
startTransaction
方法开启事务。示例代码(以 Node.js 驱动为例):
const { MongoClient } = require('mongodb');
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
async function run() {
try {
await client.connect();
const session = client.startSession();
session.startTransaction();
// 后续事务操作代码
} finally {
await client.close();
}
}
run().catch(console.dir);
- 提交事务:
完成所有事务操作后,调用
commitTransaction
方法提交事务。示例:
async function run() {
try {
await client.connect();
const session = client.startSession();
session.startTransaction();
const db = client.db('test');
const collection = db.collection('documents');
await collection.insertOne({ data: 'test' }, { session });
await session.commitTransaction();
} finally {
await client.close();
}
}
run().catch(console.dir);
- 回滚事务:
在事务执行过程中,如果出现错误,调用
abortTransaction
方法回滚事务。示例:
async function run() {
try {
await client.connect();
const session = client.startSession();
session.startTransaction();
const db = client.db('test');
const collection = db.collection('documents');
await collection.insertOne({ data: 'test' }, { session });
throw new Error('模拟错误');
await session.commitTransaction();
} catch (e) {
const session = client.startSession();
await session.abortTransaction();
} finally {
await client.close();
}
}
run().catch(console.dir);
内部实现原理
- 两阶段提交(2PC):
- 准备阶段:MongoDB 协调者节点(通常是主节点)向所有参与事务的节点发送准备消息,每个节点执行事务操作,但不提交更改,而是将更改记录在日志中,并向协调者返回准备成功或失败的响应。
- 提交阶段:如果所有节点都准备成功,协调者向所有节点发送提交消息,节点将事务更改正式提交到数据库;如果有任何一个节点准备失败,协调者向所有节点发送回滚消息,节点回滚事务更改。
- 保证数据一致性:
- 写前日志(WAL):在事务操作过程中,所有更改先写入 WAL,确保即使系统崩溃,数据也能恢复到事务执行前的状态。
- 多数写确认:对于复制集,事务提交需要多数节点确认写操作,保证数据在多个副本间的一致性。如果部分节点故障,只要多数节点可用,事务仍能正确提交或回滚,防止数据分裂。