前言
MongoDB 是当今最流行的 NoSQL 数据库之一,它的高性能和灵活性为各种应用场景提供了解决方案。然而,在一些需要强一致性的场景下,它的事务处理能力不够强大,这也一度成为其被批评的原因之一。
随着 MongoDB 4.0 和 4.2 的发布,它们都提供了原生的事务支持,让开发者在更为意外的情况下轻松地处理了各种操作。本文将深入探讨 MongoDB 的事务处理原理,并使用具体的示例代码演示 MongoDB 的事务处理实践。
MongoDB 事务基础知识
MongoDB 原生事务处理仅针对副本集和分片集群,由于仅有这两种部署方式才能有效保证事务的一致性。在 MongoDB 中,一个事务由多个操作序列组成,每个操作都可以是插入、更新、删除等。MongoDB 的事务一般在单个文档中执行,因此在事务处理时需要注意数据结构的规范性。
MongoDB 支持的事务类型包括:
- 读事务:执行读取操作的事务
- 写事务:执行更新、插入或删除操作的事务
- 混合事务:同时包含读取和写入操作的事务
MongoDB 的事务处理使用类似于 SQL 事务的 Commit 和 Rollback 两个操作。Commit 操作用于提交事务并持久化所有更改,Rollback 操作用于撤销当前事务,并且撤销的所有更改都会被回滚。
MongoDB 集群架构
在进行实际的 MongoDB 事务处理时,需要对 MongoDB 的集群架构进行了解。MongoDB 支持两种常见的部署方式:单节点和分布式集群。单节点部署方式指的是将 MongoDB 程序和数据部署在同一主机上;分布式集群部署方式指的是将 MongoDB 数据分布到多个不同的主机上,以实现高可用性、负载均衡和数据复制。
在 MongoDB 分布式集群中,主服务器主要用于写入、检索数据和查询管理工作,而副本集合中的子节点则用于自动处理主服务器失效情况下的容错恢复操作,以保证集群的稳定性和高可用性。MongoDB 分布式架构如下所示:
MongoDB 事务处理实践
准备工作
首先,需要安装 MongoDB 4.0 或 4.2 版本。在本文的实践中,将使用 MongoDB 4.2 版本。安装完毕后,启动 MongoDB 服务:
mongod --replSet "rs0"
启动服务后,需要创建一个副本集。在 MongoDB shell 中执行如下命令:
rs.initiate()
这个命令会初始化一个副本集,并且当你需要加入新的节点时,可以使用同样的 rs.add() 命令。
实验 1:写事务
在 MongoDB shell 中执行如下代码,创建一个测试数据库和集合,并插入一条测试数据:
use test db.createCollection("students") db.students.insertOne({"name":"zhangsan","age":18})
接着,在 shell 中新开一个会话,同时开启一个事务:
session = db.getMongo().startSession() session.startTransaction()
这里需要注意,MongoDB 原生事务是 session 绑定的,且每个 session 只能绑定一个事务。开启事务后,接下来可以执行 MongoDB 的增、删、改操作:
db.students.insertOne({"name":"lisi","age":20}) db.students.updateOne({"name":"zhangsan"},{$set:{"age":30}})
在转账等操作时,如果事务中出现任何一个操作失败,那么操作如果会自动回滚。因此,在本实验中,需要添加一个抛出异常的逻辑:
throw new Error("transaction fail")
执行完以上操作后,提交事务并结束 session:
session.commitTransaction() session.endSession()
实验 2:增加事务性安全
除了上述实验的基本写操作外,当在实际应用中执行事务操作时,还需要考虑事务性安全。例如,在实例 1 中的例子中,如果在写入操作期间,另一个 shell 中执行了相同的写操作,那么两个操作将会相互冲突并被回退。在这种情况下,通常需要使用乐观并发控制(Optimistic Concurrency Control,简称 OCC)。
OCC 是指数据库在执行修改操作时,会对记录进行版本控制管理。例如,当一个事务执行写操作时,MongoDB 会记录该事务当前版本的值。在另一个并发事务执行写操作时,MongoDB 会先检查当前事务版本编号是否大于该记录的版本编号,如果大于,则表示当前版本已经过时,需要更新它。在这个过程中,如果两个事务同时操作同一个文档,那么写操作将完成后,版本最新的事务会覆盖版本较旧的事务,并删除该事务的原有所有更改。
假设有两个 shell 会话,session1 和 session2,分别执行以下代码:
session1 执行:
session1.startTransaction() db.students.updateOne({"name":"zhangsan"},{$inc:{"age":1}}) session1.commitTransaction() # 确认事务,当前 version 提交 session1.endSession() # session 终止
session2 执行:
session2.startTransaction() db.students.updateOne({"name":"zhangsan"},{$inc:{"age":1}}"version":2}) session2.commitTransaction() # 确认事务,当前 version 提交
在 session2 中通过增加 version 字段属性值来更新文档,MongoDB 会判断 session2 中 MongoClient 内部的版本值(version)比内部储存的文档 version 大了多少,然后更新内部文档的 version 属性,然后更新文档 age 的值。
更加详细的实践操作可参考 MongoDB 官方文档。
结论
在本文中,我们介绍了 MongoDB 的事务处理原理和实践,以及 MongoDB 的集群架构和 MongoDB 事务性安全方案。我们希望这篇文章能够为初学者和 MongoDB 专业人士提供参考和指导,帮助他们更好地适应 MongoDB 的事务处理。如果您对 MongoDB 有进一步的疑问,我们建议您查看 MongoDB 官方文档,以获取更加详细的信息和指导。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/672341002e7021665e0f17d3