作者:赵师的工作日(赵明中)
现役Oracle ACE、MySQL 8.0 ocp、TiDB PCTA\PCTP、Elasticsearch Certified Engineer
微信号:mzzhao23
微信公众号:赵师的工作日
墨天轮社区:赵师的工作日
CSND:赵师的工作日
MongoDB 虽然是 NoSQL 数据库,但其随着版本迭代,事务支持逐渐完善。MongoDB从 4.x 版本开始引入了多文档事务(Multi-Document Transactions),并在后续版本进一步增强了事务的功能和性能,提供了更高的稳定性和更丰富的事务特性。
一、MongoDB 事务概述
在 MongoDB 中,事务是指一组操作,这些操作要么全部成功(提交),要么全部失败(回滚)。事务能够保证原子性、一致性、隔离性和持久性(ACID 特性),使得 MongoDB 不再仅仅是一个面向单文档的数据库,而是能够处理跨文档、跨集合的多操作事务。
事务特性
MongoDB 事务的基本特性与传统关系型数据库相似,提供以下 ACID 支持:
- 原子性(Atomicity):事务内的操作要么完全成功,要么完全失败。即使发生错误,数据库状态会回滚到事务开始前的状态。
- 一致性(Consistency):事务开始前后,数据库始终处于一致性状态,确保数据不违反约束。
- 隔离性(Isolation):事务的执行互不干扰,事务对外部操作不可见,直到事务提交。
- 持久性(Durability):一旦事务提交,数据会被永久保存,即使发生系统崩溃,数据也不会丢失。
二、MongoDB 事务的增强
在 MongoDB 4+版本后,事务的支持做了很多增强和优化,尤其是在性能和稳定性方面。
- 优化的写入性能:特别是针对大规模并发事务的处理。
- 跨集群事务:引入了对跨分片集群事务的改进,支持在分布式环境下执行事务。
- 改进的事务日志管理:优化了事务日志的管理,使得事务的恢复和回滚更加高效。
- 事务锁优化:对事务锁进行了优化,减少了锁竞争,提升了并发事务的执行效率。
三、MongoDB 事务的工作原理
MongoDB 事务的实现基于 “两阶段提交”(2PC, Two-Phase Commit)协议。事务的执行大体可以分为以下两个阶段:
1、事务开始阶段
在事务开始时,MongoDB 会分配一个唯一的事务标识符(Transaction ID,TxnID),该标识符用于跟踪事务的状态。此时,MongoDB 会锁定相关数据,防止其他事务对这些数据进行修改,直到事务提交或回滚。
2、事务提交与回滚
- 提交阶段:当事务中的操作成功执行时,MongoDB 会将操作记录在事务日志中,并提交事务。提交后,相关数据的修改会永久生效,并对其他事务可见。
- 回滚阶段:如果事务中出现任何错误或在事务提交前发生故障,MongoDB 会回滚所有已执行的操作,恢复数据到事务开始前的状态。
同时在4+ 版本中,事务日志(oplog)的管理得到了进一步优化,事务的回滚操作更加高效。所有与事务相关的操作都会记录在一个专门的事务日志中,确保数据的一致性和持久性。
四、MongoDB 事务的使用
MongoDB 事务主要用于需要跨多个文档、跨多个集合或者跨多个数据库的操作。下面通过一个简单的例子来说明
1、事务的启动
在 MongoDB 中,事务是通过客户端的会话(session)进行管理的。每个会话可以开启一个或多个事务。
const { MongoClient } = require('mongodb');
async function runTransaction() {
const client = new MongoClient('mongodb://localhost:27017', { useUnifiedTopology: true });
await client.connect();
const session = client.startSession(); // 创建会话
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'majority' },
writeConcern: { w: 'majority' },
};
try {
// 开始事务
session.startTransaction(transactionOptions);
const usersCollection = client.db('test').collection('users');
const ordersCollection = client.db('test').collection('orders');
// 执行事务中的操作
await usersCollection.updateOne({ _id: 1 }, { $set: { name: 'Alice' } }, { session });
await ordersCollection.updateOne({ userId: 1 }, { $set: { status: 'shipped' } }, { session });
// 提交事务
await session.commitTransaction();
console.log('Transaction committed successfully');
} catch (error) {
// 如果发生错误,回滚事务
await session.abortTransaction();
console.log('Transaction aborted due to error:', error);
} finally {
session.endSession();
await client.close();
}
}
runTransaction().catch(console.error);
2、事务管理
在上述代码中,我们使用 startSession() 方法创建了一个会话,通过该会话启动了一个事务。事务内的操作需要显式地传递 session 对象,确保这些操作在同一个事务内执行。在事务结束时,调用 commitTransaction() 提交事务,如果发生错误则调用 abortTransaction() 回滚事务。
五、事务的性能考虑与优化
在 MongoDB 4+版本后,虽然事务性能已大幅提升,但多文档事务相较于单文档操作仍然存在性能开销。为了尽量减少事务对性能的影响,以下几点值得注意:
1、合理设计事务范围
尽量缩小事务操作的范围,只将必要的操作包含在事务中。避免将大量的读写操作放入同一个事务,以减少锁竞争和提高事务执行效率。
2、使用适当的事务隔离级别
MongoDB 支持不同级别的事务隔离(如 readConcern 和 writeConcern),在大多数情况下,默认的 majority 隔离级别已经足够。但在高并发场景下,可以根据实际需求调整隔离级别,以实现性能和一致性的平衡。
3、事务日志管理
在高并发环境中,事务日志的管理尤为重要。MongoDB 4+ 版本优化了事务日志的存储与恢复机制,确保即使在故障恢复后,事务也能保持一致性。然而,频繁的大事务操作可能导致日志文件的快速增长,影响系统性能。因此,合理规划数据库的存储空间与日志文件大小至关重要。