MongoDB数据库两阶段提交实现事务的方法详解

本文实例讲述了MongoDB数据库两阶段提交实现事务的方法。分享给大家供大家参考,具体如下:

MongoDB数据库中操作单个文档总是原子性的,然而,涉及多个文档的操作,通常被作为一个“事务”,而不是原子性的。因为文档可以是相当复杂并且包含多个嵌套文档,单文档的原子性对许多实际用例提供了支持。尽管单文档操作是原子性的,在某些情况下,需要多文档事务。在这些情况下,使用两阶段提交,提供这些类型的多文档更新支持。因为文档可以表示为Pending数据和状态,可以使用一个两阶段提交确保数据是一致的,在一个错误的情况下,事务前的状态是可恢复的。

事务最常见的例子是以可靠的方式从A账户转账到B账户,在关系型数据库中,此操作将从A账户减掉金额和给B账户增加金额的操作封装在单个原子事务中。在MongoDB中,可以使用两阶段提交达到相同的效果。本文中的所有示例使用mongo shell与数据库进行交互,并假设有两个集合:首先,一个名为accounts的集合存储每个账户的文档数据,另一个名为transactions的集合存储事务本身。

首先创建两个名为A和B的账户,使用下面的命令:

db.accounts.save({name: "A", balance: 1000, pendingTransactions: []})
db.accounts.save({name: "B", balance: 1000, pendingTransactions: []})

使用find()方法验证这两个操作已经成功:

db.accounts.find()

mongo会返回两个类似下面的文档:

{ "_id" : ObjectId("4d7bc66cb8a04f512696151f"), "name" : "A", "balance" : 1000, "pendingTransactions" : [ ] }
{ "_id" : ObjectId("4d7bc67bb8a04f5126961520"), "name" : "B", "balance" : 1000, "pendingTransactions" : [ ] }

事务过程:

设置事务初始状态initial:

通过插入下面的文档创建transaction集合,transaction文档持有源(source)和目标(destination),它们引用自accounts集合文档的字段名,以及value字段表示改变balance字段数量的数据。最后,state字段反映事务的当前状态。

代码如下:

db.transactions.save({source: "A", destination: "B", value: 100, state: "initial"})

验证这个操作已经成功,使用find()

db.transactions.find()

这个操作会返回一个类似下面的文档:

代码如下:

{ "_id" : ObjectId("4d7bc7a8b8a04f5126961522"), "source" : "A", "destination" : "B", "value" : 100, "state" : "initial" }

切换事务到Pending状态:

在修改accounts集合记录之前,将事务状态从initial设置为pending。使用findOne()方法将transaction文档赋值给shell会话中的局部变量t:

t = db.transactions.findOne({state: "initial"})

变量t创建后,shell将返回它的值,将会看到如下的输出:

代码如下:

{ "_id" : ObjectId("4d7bc7a8b8a04f5126961522"), "source" : "A", "destination" : "B", "value" : 100, "state" : "initial" }

使用update()改变state的值为pending:

db.transactions.update({_id: t._id}, {$set: {state: "pending"}})
db.transactions.find()

find()操作将返回transaction集合的内容,类似下面:

代码如下:

{ "_id" : ObjectId("4d7bc7a8b8a04f5126961522"), "source" : "A", "destination" : "B", "value" : 100, "state" : "pending" }

将事务应用到两个账户:

使用update()方法应用事务到两个账户。在update()查询中,条件pendingTransactions:{$ne:t._id}阻止事务更新账户,如果账户的pendingTransaction字段包含事务t的_id:

db.accounts.update(
 { name: t.source, pendingTransactions: { $ne: t._id } },
 { $inc: { balance: -t.value }, $push: { pendingTransactions: t._id } }
)
db.accounts.update(
 { name: t.destination, pendingTransactions: { $ne: t._id } },
 { $inc: { balance: t.value }, $push: { pendingTransactions: t._id } }
)
db.accounts.find()

find()操作将返回accounts集合的内容,现在应该类似于下面的内容:

{ "_id" : ObjectId("4d7bc97fb8a04f5126961523"), "balance" : 900, "name" : "A", "pendingTransactions" : [ ObjectId("4d7bc7a8b8a04f5126961522") ] }
{ "_id" : ObjectId("4d7bc984b8a04f5126961524"), "balance" : 1100, "name" : "B", "pendingTransactions" : [ ObjectId("4d7bc7a8b8a04f5126961522") ] }

设置事务状态为committed:

使用下面的update()操作设置事务的状态为committed:

db.transactions.update({_id: t._id}, {$set: {state: "committed"}})
db.transactions.find()

find()操作发回transactions集合的内容,现在应该类似下面的内容:

代码如下:

{ "_id" : ObjectId("4d7bc7a8b8a04f5126961522"), "destination" : "B", "source" : "A", "state" : "committed", "value" : 100 }

移除pending事务:

使用下面的update()操作从accounts集合中移除pending事务:

db.accounts.update({name: t.source}, {$pull: {pendingTransactions: t._id}})
db.accounts.update({name: t.destination}, {$pull: {pendingTransactions: t._id}})
db.accounts.find()

find()操作返回accounts集合内容,现在应该类似下面内容:

{ "_id" : ObjectId("4d7bc97fb8a04f5126961523"), "balance" : 900, "name" : "A", "pendingTransactions" : [ ] }
{ "_id" : ObjectId("4d7bc984b8a04f5126961524"), "balance" : 1100, "name" : "B", "pendingTransactions" : [ ] }

设置事务状态为done:

通过设置transaction文档的state为done完成事务:

db.transactions.update({_id: t._id}, {$set: {state: "done"}})
db.transactions.find()

find()操作返回transaction集合的内容,此时应该类似下面:

代码如下:

{ "_id" : ObjectId("4d7bc7a8b8a04f5126961522"), "destination" : "B", "source" : "A", "state" : "done", "value" : 100 }

从失败场景中恢复:

最重要的部分不是上面的典型例子,而是从各种失败场景中恢复未完成的事务的可能性。这部分将概述可能的失败,并提供方法从这些事件中恢复事务。这里有两种类型的失败:

1、所有发生在第一步(即设置事务的初始状态initial)之后,但在第三步(即应用事务到两个账户)之前的失败。为了还原事务,应用应该获取一个pending状态的transaction列表并且从第二步(即切换事务到pending状态)中恢复。

2、所有发生在第三步之后(即应用事务到两个账户)但在第五步(即设置事务状态为done)之前的失败。为了还原事务,应用需要获取一个committed状态的事务列表,并且从第四步(即移除pending事务)恢复。

因此应用程序总是能够恢复事务,最终达到一个一致的状态。应用程序开始捕获到每个未完成的事务时运行下面的恢复操作。你可能还希望定期运行恢复操作,以确保数据处于一致状态。达成一致状态所需要的时间取决于应用程序需要多长时间恢复每个事务。

回滚:

在某些情况下可能需要“回滚”或“撤消”事务,当应用程序需要“取消”该事务时,或者是因为它永远需要恢复当其中一个帐户不存在的情况下,或停止现有的事务。这里有两种可能的回滚操作:

1、应用事务(即第三步)之后,你已经完全提交事务,你不应该回滚事务。相反,创建一个新的事务,切换源(源)和目标(destination)的值。

2、创建事务(即第一步)之后,在应用事务(即第三步)之前,使用下面的处理过程:

设置事务状态为canceling:

首先设置事务状态为canceling,使用下面的update()操作:

db.transactions.update({_id: t._id}, {$set: {state: "canceling"}})

撤销事务:

使用下面的操作顺序从两个账户中撤销事务:

db.accounts.update({name: t.source, pendingTransactions: t._id}, {$inc: {balance: t.value}, $pull: {pendingTransactions: t._id}})
db.accounts.update({name: t.destination, pendingTransactions: t._id}, {$inc: {balance: -t.value}, $pull: {pendingTransactions: t._id}})
db.accounts.find()

find()操作返回acounts集合的内容,应该类似下面:

{ "_id" : ObjectId("4d7bc97fb8a04f5126961523"), "balance" : 1000, "name" : "A", "pendingTransactions" : [ ] }
{ "_id" : ObjectId("4d7bc984b8a04f5126961524"), "balance" : 1000, "name" : "B", "pendingTransactions" : [ ] }

设置事务状态为canceled:

最后,使用下面的update()状态将事务状态设置为canceled:

db.transactions.update({_id: t._id}, {$set: {state: "canceled"}})

参考资料:http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/

希望本文所述对大家MongoDB数据库程序设计有所帮助。

(0)

相关推荐

  • MongoDB下根据数组大小进行查询的方法

    注意:作者使用的mongodb版本为2.4.7. 首先插入测试数据 复制代码 代码如下: db.data.insert({name:'a', num:[12,123,22,34,1]});db.data.insert({name:'b', num:[42,22]});db.data.insert({name:'c', num:[49]}); 键num对应的值是数组. 查询num的数组值具有指定大小的document 最好的方法是使用$size,例如指定大小为2,可以: 复制代码 代码如下: db

  • mongoDB 4.0事务回滚的辛酸历程探究

    前言 主管前几天发现mongoDB已经升级到4.0了,迫不及待得让我实现他期待已久的事务回滚,发现还是有很多坑啊! 下面是我将已有的本地mongoDB升级到支持事务回滚的历程,分享出来,有错误的地方欢迎指正! 以mac为例哈 部署mongodb事务回滚 1.准备工作 升级mongodb至4.0.0 $ brew upgrade mongodb 升级或安装mongodb.js v3.1.0 以上 $ npm i mongodb --save-dev 坑 升级完上述后,db.js里连接mongo时,

  • 1亿条记录的MongoDB数据库随机查询性能测试

    mongdb性能压力测试,随机查询,数据量1亿条记录 操作系统centos6.4x64位 从测试结果看,当mongodb将数据全部载入到内存后,查询速度根据文档的大小,性能瓶颈通常会是在网络流量和CPU的处理性能(该次测试中当数据全部在内存后,纯粹的查询速度可以稳定在10W/S左右,系统load可以维持在1以下,由于此时CPU已经被使用到极限了,当并发再大时load值会直线飙升,性能急剧下降). 压力生成服务器与Mongodb服务器基本配置 cpu型号:Intel(R) Xeon(R) CPU

  • mongodb中使用distinct去重的简单方法

    MongoDB的destinct命令是获取特定字段中不同值列表.该命令适用于普通字段,数组字段和数组内嵌文档. mongodb的distinct的语句: 复制代码 代码如下: db.users.distinct('last_name') 等同于 SQL 语句: 复制代码 代码如下: select DISTINCT last_name from users 表示的是根据指定的字段返回不同的记录集. 一个简单的实例: // > db.addresses.insert({"zip-code&qu

  • MongoDB 语法使用小结

    他支持的数据结构非常松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型.Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引. 它的特点是高性能.易部署.易使用,存储数据非常方便. 1. MongoDB的获取和安装 (1)获取地址 http://www.mongodb.org/downloads 根据自己需要选择相应的版本,linux下可以使用wget 命令. (2)解压

  • mongodb 添加用户及权限设置详解

    例如:mysql安装配置好后,有一个自带的mysql数据库,里面有一张user表,用来存放用户,以及用户权限,而mongodb这个最像关系型的数据库,有没有这样的表呢. 一,掌握权限,理解下面4条基本上就差不多 1,mongodb是没有默认管理员账号,所以要先添加管理员账号,在开启权限认证. 2,切换到admin数据库,添加的账号才是管理员账号. 3,用户只能在用户所在数据库登录,包括管理员账号. 4,管理员可以管理所有数据库,但是不能直接管理其他数据库,要先在admin数据库认证后才可以.这一

  • mongodb与mysql命令详细对比

    传统的关系数据库一般由数据库(database).表(table).记录(record)三个层次概念组成,MongoDB是由数据库(database).集合(collection).文档对象(document)三个层次组成.MongoDB对于关系型数据库里的表,但是集合中没有列.行和关系概念,这体现了模式自由的特点. MySQL MongoDB 说明 mysqld mongod 服务器守护进程 mysql mongo 客户端工具 mysqldump mongodump 逻辑备份工具 mysql

  • MongoDB各种查询操作详解

    一.find操作 MongoDB中使用find来进行查询,通过指定find的第一个参数可以实现全部和部分查询. 1.查询全部 空的查询文档{}会匹配集合的全部内容.如果不指定查询文档,默认就是{}. 2.部分查询 3.键的筛选 键的筛选是查询时只返回自己感兴趣的键值,通过指定find的第二个参数来实现.这样可以节省传输的数据量,又能节省客户端解码文档的时间和内存消耗. 查询时,数据库所关心的查询文档的值必须是常量. 二.查询条件 1.比较查询 $lt,$lte,$gt,$gte,$ne和<,<

  • Linux系统下MongoDB的简单安装与基本操作

    Mongo DB ,是目前在IT行业非常流行的一种非关系型数据库(NoSql),其灵活的数据存储方式,备受当前IT从业人员的青睐.Mongo DB很好的实现了面向对象的思想(OO思想),在Mongo DB中 每一条记录都是一个Document对象.Mongo DB最大的优势在于所有的数据持久操作都无需开发人员手动编写SQL语句,直接调用方法就可以轻松的实现CRUD操作. 一.下载mongodb 前往mongodb官网下载页面:https://www.mongodb.org/downloads下载

  • mongodb 查看数据库和表大小

    1,查看数据库 > db.stats(); { "db" : "test", //当前数据库 "collections" : 3, //当前数据库多少表 "objects" : 4, //当前数据库所有表多少条数据 "avgObjSize" : 51, //每条数据的平均大小 "dataSize" : 204, //所有数据的总大小 "storageSize"

  • PHP中MongoDB数据库的连接、添加、修改、查询、删除等操作实例

    PHP 扩展mongon.mod.dll下载http://cn.php.net/manual/en/mongo.installation.php#mongo.installation.windows 然后php.ini添加 extension=php_mongo.dll 最后phpinfo() 查找到 表标PHP已经自带了mongo功能,你就可以操作下面的代码(但是你必须有安装mongodb服务器) 一.连接数据库 使用下面的代码创建一个数据库链接 复制代码 代码如下: <?php $conne

随机推荐