【MongoDB篇】MongoDB的事务操作!
目录
- 引言
- 第一节:什么是事务? (ACID 原则)
- 第二节:MongoDB 的演进:多文档 ACID 事务的到来!🎉
- 第三节:事务的“玩法”——如何执行一个事务?💻🤝
- 第四节:读写关注 (Read/Write Concern) 在事务中 📚✍️🛡️
- 第五节:事务的限制与考量 🤔📏🐢
- 第六节:何时需要请出事务这尊大佛?⚖️
- 第七节:总结事务操作!🎉🔒
🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
其他优质专栏: 【🎇SpringBoot】【🎉多线程】【🎨Redis】【✨设计模式专栏(已完结)】…等
如果喜欢作者的讲解方式,可以点赞收藏加关注,你的支持就是我的动力
✨更多文章请看个人主页: 码熔burning
看之前可以先了解一下MongoDB是什么:【MongoDB篇】万字带你初识MongoDB!
引言
通过之前的文章,咱们已经学会了对单个文档的 CRUD 操作,也学会了用索引加速查询,用聚合框架分析数据。这些都很重要,但有时候,你的一个业务操作,需要同时修改多个文档,甚至多个集合里的数据!
想象一下,你正在处理一个电商订单:
- 创建一个新的订单文档。
- 更新商品库存,减少购买数量。
- 更新用户积分或余额。
这三个操作,必须要么全部成功,要么全部失败!如果只成功了第一步和第二步,第三步失败了,那你的订单数据就处于一个“不一致”的状态:订单创建了,库存扣减了,但用户积分没更新,或者更糟,如果是支付场景,钱扣了但订单没创建!😱 这是绝对不能接受的!
在传统的关系型数据库里,用事务 (Transaction) 来解决这个问题是家常便饭。那在以灵活性著称的 MongoDB 里,事务又是怎么回事呢?是不是像以前听说的那样不支持事务呢?😏
别急!哥告诉你,MongoDB 已经完全支持多文档 ACID 事务了! 🎉 这是 MongoDB 近年来最重要的发展之一!
第一节:什么是事务? (ACID 原则)
事务是数据库管理系统执行过程中的一个逻辑单位,它包含一个或多个数据库操作,这些操作被视为一个不可分割的整体。事务的设计目标是确保数据的一致性 (Consistency),通常通过遵循 ACID 原则来实现:
- 原子性 (Atomicity) ⚛️:一个事务中的所有操作,要么全部成功提交,要么全部失败回滚。不存在只完成一部分的情况。就像化学里的原子,不可再分!
- 一致性 (Consistency) ✅:事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。不会破坏预定的业务规则和数据完整性。
- 隔离性 (Isolation) 隔离:并发执行的事务之间互不影响。一个事务的执行不应该看到其他并发事务的中间状态。就像每个事务都在自己的“沙盒”里运行一样!🏖️
- 持久性 (Durability) 🛡️:一旦事务成功提交,它对数据库的改变就是永久性的,即使系统发生故障(如断电),提交的数据也不会丢失。数据安全落地,高枕无忧!💤
第二节:MongoDB 的演进:多文档 ACID 事务的到来!🎉
早期的 MongoDB 版本确实不直接支持跨多个文档的 ACID 事务,这在一定程度上限制了它在需要严格数据一致性的复杂业务场景中的应用。
但是!随着技术的进步和社区的需求,从 MongoDB 4.0 版本开始,MongoDB 引入了对复制集 (Replica Sets) 上的多文档 ACID 事务的支持! 💪 随后在 MongoDB 4.2 版本,更是将这个能力扩展到了分片集群 (Sharded Clusters) 上!
这意味着,从 MongoDB 4.0/4.2 及以后的版本开始,你可以在 MongoDB 中安全地执行跨多个文档、多个集合、甚至多个分片的原子性操作了!👍 MongoDB 不再只是“单文档原子性”的数据库了!
注意: 多文档事务需要 MongoDB 部署为复制集 (Replica Set) 或分片集群 (Sharded Cluster)。它不适用于独立的 mongod
实例!🧱🧱🧱
第三节:事务的“玩法”——如何执行一个事务?💻🤝
在 MongoDB 中执行事务,需要通过会话 (Session) 来进行。一个会话是一系列逻辑上相关的操作的容器。事务是发生在一个会话中的。
事务的生命周期:
执行一个事务通常遵循以下步骤:
-
启动一个会话 (Start a Session):获取一个客户端会话对象。
session = db.getMongo().startSession();
或者在使用驱动程序时,通过 MongoClient 对象获取会话。
-
在会话中启动一个事务 (Start a Transaction):
session.startTransaction();
-
执行事务操作 (Perform Operations):在事务内部执行你的读写操作。所有需要在同一个事务中执行的数据库操作,都必须使用同一个会话对象来发起!
// 使用会话对象访问数据库和集合,并执行操作 session.getDatabase("mydatabase").collection("accounts").updateOne({ account_id: "A001" },{ $inc: { balance: -100 } } );session.getDatabase("mydatabase").collection("accounts").updateOne({ account_id: "A002" },{ $inc: { balance: 100 } } );// 你也可以在事务中执行读取操作,通常设置特定的读关注 (Read Concern) let accountA = session.getDatabase("mydatabase").collection("accounts").findOne({ account_id: "A001" });
注意: 在事务中,你可以执行大部分的 CRUD 操作(
insert
,update
,delete
,find
),以及一些索引操作。但有些操作是不允许在事务中执行的,比如创建/删除数据库、创建/删除集合、创建索引(除了某些特定类型的临时索引)、修改用户/角色等管理类操作。 -
提交事务 (Commit Transaction):如果所有操作都成功,提交事务,所有修改将被原子性地保存到数据库。
session.commitTransaction(); print("Transaction committed.");
-
中止/回滚事务 (Abort Transaction):如果在事务执行过程中发生任何错误,或者你想取消事务,可以中止事务。中止后,事务中的所有修改都将被回滚,数据库回到事务开始前的状态。
session.abortTransaction(); print("Transaction aborted.");
-
结束会话 (End Session):事务完成后(无论提交还是中止),结束会话并释放资源。
session.endSession();
典型的事务执行流程 (带错误处理):
在实际应用中,执行事务需要结合错误处理和重试逻辑,因为事务可能会由于各种原因失败(比如网络问题、写冲突、内存不足等)。一个健壮的事务执行通常会放在一个循环里,在特定错误发生时进行重试。
这是一个简化的 Shell 示例:
let session;
try {session = db.getMongo().startSession();session.startTransaction({readConcern: { level: 'snapshot' }, // 在事务中常用 snapshot 读关注writeConcern: { w: 'majority' } // 在事务中常用 majority 写关注});// --- 在这里执行你的事务操作 ---print("Executing transaction operations...");// 假设这是一个转账操作session.getDatabase("mydatabase").collection("accounts").updateOne({ account_id: "A001" },{ $inc: { balance: -100 } });print("Deducted from A001");session.getDatabase("mydatabase").collection("accounts").updateOne({ account_id: "A002" },{ $inc: { balance: 100 } });print("Credited to A002");// --- 事务操作结束 ---session.commitTransaction(); // 提交事务print("Transaction committed successfully.");} catch (error) {// 捕获错误,中止事务print("Transaction encountered an error: " + error);if (session && session.inTransaction()) {session.abortTransaction();print("Transaction aborted.");}// 在这里添加重试逻辑,对于某些可重试的错误进行重试// ...
} finally {// 结束会话if (session) {session.endSession();print("Session ended.");}
}
实际的重试逻辑会更复杂,需要判断错误的类型(是否是可重试的事务错误)。
第四节:读写关注 (Read/Write Concern) 在事务中 📚✍️🛡️
事务中的读写操作也会受到读关注 (Read Concern) 和写关注 (Write Concern) 的影响,它们决定了事务中读取数据的一致性和写入数据的持久性。
- 读关注 (Read Concern):决定了你从 MongoDB 读取数据时,能看到的数据级别。
- 在事务中,通常推荐使用
'snapshot'
或'majority'
读关注。 'snapshot'
:提供了一个事务开始时的数据快照,保证了事务内部读到的是一个一致性的视图,不会读到其他并发事务未提交的数据。这是实现事务隔离性 (Isolation) 的关键。'majority'
:保证读到的数据已经被大多数节点确认,持久性更高。
- 在事务中,通常推荐使用
- 写关注 (Write Concern):决定了一个写入操作需要等待多少个节点确认才算成功。
- 在事务中,通常推荐使用
'majority'
写关注({ w: 'majority' }
)。这保证了事务提交的数据已经被大多数节点持久化,即使 Primary 节点宕机,数据也不会丢失,提高了持久性 (Durability)。
- 在事务中,通常推荐使用
在启动事务时,可以在 startTransaction()
方法的 options 参数中指定读关注和写关注,它们将应用于该事务中的所有操作(除非在单个操作中被覆盖)。
第五节:事务的限制与考量 🤔📏🐢
虽然多文档事务非常强大,但它并不是万能的,也有一些限制和需要注意的地方:
- 性能开销:相比单文档操作,事务会引入额外的协调和日志记录开销,因此性能会相对较低。只在你确实需要保证多文档操作原子性时使用事务! 不要滥用。⚡️➡️🐢
- 环境要求:必须在复制集或分片集群上使用,独立的
mongod
实例不支持。 - 事务大小和时间限制:事务不适合处理非常大的数据集或长时间运行。默认有操作数量和执行时间的限制。复杂的、长时间的事务更容易失败。
- 某些操作不允许:如前所述,数据库/集合的创建/删除、索引创建(除了某些临时索引)、用户管理等操作不能在事务中执行。如果你需要在事务中创建集合,可以先在事务外手动创建,或者在事务内执行插入操作,MongoDB 会自动创建(但如果插入失败导致事务回滚,这个集合可能会保留下来,需要额外处理)。
- 对 capped collections 的限制: capped collections 不能参与事务。
- 锁和并发:事务会持有锁,长时间的事务可能会影响并发性能。
- 错误处理和重试:编写使用事务的代码时,必须实现适当的错误处理和重试逻辑,以应对可能的事务失败。
第六节:何时需要请出事务这尊大佛?⚖️
尽管有性能开销和限制,但在以下这些场景中,多文档事务是确保数据一致性不可或缺的工具:
- 财务类应用:转账、支付、记账等,任何涉及到从一个地方扣除并添加到另一个地方的操作。💰➡️💳
- 订单管理:创建订单、更新库存、生成支付记录等,需要保证订单信息和库存状态同步更新。🛍️📦
- 工作流或状态机:一系列相互依赖的操作步骤,必须全部成功才能进入下一状态,否则需要回滚到原始状态。🔃
- 需要跨多个文档强制唯一性:虽然唯一索引只能作用于单个字段,但如果你的业务逻辑需要确保某个逻辑实体(由多个文档组成)的唯一性,事务可以用来检查和保证。
- 需要在读取数据后立即基于读取的数据进行修改:事务的隔离性可以保证你在事务中读到的数据不会被其他并发事务所修改,避免“脏读”、“不可重复读”等问题,确保你基于的数据是最新的快照。
总的来说,如果你需要保证多个独立的写操作“一起成功或一起失败”,那么就应该考虑使用多文档事务。
第七节:总结事务操作!🎉🔒
太棒了!我们详细讲解了 MongoDB 的多文档 ACID 事务!从事务的基本概念和 ACID 原则,到 MongoDB 如何实现单文档原子性和为什么需要多文档事务,再到如何在 MongoDB 中开启、执行、提交、中止事务,以及事务中的读写关注、限制和最佳实践。
核心要点回顾:
- ACID 原则确保事务的数据一致性。
- MongoDB 原本提供单文档原子性。
- MongoDB 4.0+ 支持复制集/分片集群上的多文档 ACID 事务。
- 事务通过会话启动和管理。
- 事务生命周期包括启动会话 -> 启动事务 -> 执行操作 -> 提交/中止 -> 结束会话。
- 事务中通常使用
'snapshot'
读关注和'majority'
写关注。 - 事务有性能开销、环境要求(复制集/分片)、大小/时间限制、操作限制等。
- 必须做好错误处理和重试。
- 必要时才使用事务,不要滥用。
- 典型场景包括金融、订单、工作流等。
掌握了事务,你就掌握了在 MongoDB 中处理复杂业务逻辑和保证数据一致性的利器!这是一个非常重要的技能!💪
继续加油!让你的数据一致性无懈可击吧!🚀
了解数据库操作请看:【MongoDB篇】MongoDB的数据库操作!
了解集合操作请看:【MongoDB篇】MongoDB的集合操作!
了解文档操作请看:【MongoDB篇】MongoDB的文档操作!
了解索引操作请看:【MongoDB篇】MongoDB的索引操作!
了解聚合操作请看:【MongoDB篇】MongoDB的聚合框架!