MongoDB 事务支持详解
MongoDB 4.0版本开始支持了多文档事务,这是MongoDB一个重要的里程碑,意味着Mongodb可以用来存储具有ACID特性的关系型数据了。
事务的基本概念
事务是指一组数据库操作,它们被视为一个工作单元,要么全部执行成功,要么全部失败执行回滚。MongoDB中的事务遵循的是“all-or-nothing”的原则。
一个事务通常包括以下四个属性:“原子性”、“一致性”、“隔离性”和“持久性”。
- 原子性:事务执行的所有操作作为一个整体被执行,要么所有操作都成功完成,要么所有操作都不执行。如果在事务执行期间发生错误,所有的操作都将被回滚。
- 一致性:事务执行前后的状态应该保持一致,事务结束后应该符合预期的状态。
- 隔离性:多个事务并发执行时,事务之间应该是相互隔离,每个事务应该感觉不到其他事务的存在。
- 持久性:一旦事务提交,那么事务执行的结果必须得到持久化,不能丢失。
事务的使用限制
在使用MongoDB事务时,可能会遇到一些限制:
- 处理事务的集合必须是分片集合或副本集合;
- 在同一事务中,不能对非同一分片的集合进行读写操作;
- 在同一事务中,不能跨越包含多个文档的单个shard或primary replica set;
- 在事务中,所有读写操作必须在一个session中完成。
事务的使用
在MongoDB中,事务的使用可以通过以下方式进行:
1. 开始事务
首先,需要使用MongoDB的事务API获取一个会话对象,然后使用这个会话对象开始一个事务。
session.startTransaction();
2. 执行事务中的操作
执行事务中的所有操作,这些操作可以包括增删改查等操作。
db.collection1.insertOne({name: 'Tom', age: 25}, {session});
db.collection2.updateOne({name: 'Tom'}, {$set: {age: 26}}, {session});
db.collection3.deleteOne({name: 'Tom'}, {session});
需要注意的是,每个操作中都需要传入会话对象,这样MongoDB才能知道这些操作属于哪个事务。
3. 提交或回滚事务
当事务中的所有操作都执行完毕后,可以使用以下方式提交事务:
session.commitTransaction();
如果事务执行过程中出现了错误,需要回滚事务:
session.abortTransaction();
为了保障事务操作的一致性和隔离性,建议使用带有事务回调函数的方式。
4. 带有事务回调函数
将事务中的所有操作放入一个回调函数中,当事务操作成功执行时,自动提交事务,否则回滚事务。
session.withTransaction(async () => {
db.collection1.insertOne({name: 'Tom', age: 25}, {session});
db.collection2.updateOne({name: 'Tom'}, {$set: {age: 26}}, {session});
db.collection3.deleteOne({name: 'Tom'}, {session});
});
事务的示例
1. 插入一条文档并同时修改另一条文档
假设某个文档有这样的结构:
{
"_id": 1,
"name": "Tom",
"age": 25,
"bankAccount": {
"balance": 100,
"currency": "USD"
}
}
现在,我们需要同时将Tom的年龄加1,并将Tom的银行账户中的余额减去50美元。这个操作就需要使用事务。
session.withTransaction(async () => {
const tom = await db.collection('users').findOne({_id: 1}, {session});
const balance = tom.bankAccount.balance;
if (balance >= 50) {
await db.collection('users').updateOne({_id: 1}, {$inc: {age: 1}}, {session});
await db.collection('users').updateOne({_id: 1}, {$inc: {'bankAccount.balance': -50}}, {session});
} else {
throw new Error('Not enough balance.');
}
});
代码中,首先使用findOne方法查询Tom的文档,并将会话对象传递给了findOne方法。然后获取Tom的余额,根据余额进行后续的操作。注意在操作中需要使用 $inc 操作符对Tom的年龄和余额进行修改。
2. 跨集合的事务
一个跨集合的事务例子是,将订单和订单明细同时插入到数据库中。
session.withTransaction(async () => {
const orderId = await db.collection('orders').insertOne({orderNo: 'ORD001', amount: 1000}, {session});
const orderDetailId = await db.collection('orderDetails').insertOne({orderId: orderId, productId: 1, quantity: 10, price: 100}, {session});
});
在这个例子中,我们首先向订单集合中插入一个订单,然后向订单明细集合中插入一个订单明细。事务保证了整个过程的原子性,如果订单或订单明细中有一个插入失败,那么整个过程会自动回滚。
总结
MongoDB的事务支持是一个令人激动的特性,它可以为我们提供关系型数据库的高度可靠性和ACID特性。在使用MongoDB的事务时,需要注意支持事务的集合类型、事务的使用限制和事务API的使用。