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

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

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

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

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

1
db.accounts.find()

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

1
2
3
4
{ "_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字段反映事务的当前状态。

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

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

1
db.transactions.find()

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

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

切换事务到Pending状态

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

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

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

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

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

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

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

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

将事务应用到两个账户

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

1
2
3
4
5
6
7
8
9
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集合的内容,现在应该类似于下面的内容:

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

设置事务状态为committed

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

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

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

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

移除pending事务

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

1
2
3
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集合内容,现在应该类似下面内容:

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

设置事务状态为done

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

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

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

1
2
{ "_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()操作:

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

撤销事务

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

1
2
3
4
5
6
7
8
9
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集合的内容,应该类似下面:

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

设置事务状态为canceled

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

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

原文地址:http://www.tuicool.com/articles/f6ZBjm

但是:其实我没有看明白这是怎么实现事务的,懂的朋友可以留言帮忙讲解下

MongoDB两阶段提交实现事务相关推荐

  1. mongodb mysql 事务_MongoDB数据库两阶段提交实现事务的方法详解 _ 蚂蚁视界

    本文实例讲述了MongoDB数据库两阶段提交实现事务的办法.分享给年夜家供年夜家参考,详细如下: MongoDB数据库中操作单个文档老是原子性的,然而,涉及多个文档的操作,通常被作为一个"事 ...

  2. oracle二阶段事物,分布式事务 两阶段提交 (2PC)

    两阶段提交(2PC) 是 Oracle Tuxedo 系统提出的 XA 分布式事务协议的其中一种实现方式. XA协议中有两个重要角色:事务协调者和事务参与者 既然叫两阶段提交,肯定是分为两个阶段. J ...

  3. 分布式事务Seata的AT模式下两阶段提交原理

    文章目录 第一阶段 1. 扫描@GlobalTransactional注解,获取全局事务XID 2. TC生成全局事务XID,记录入库 3. 执行业务逻辑,提交本地事务,记录branch_table. ...

  4. 与基础事务管理器的通信失败 存货申请_干货必读!细说分布式事务两阶段提交...

    本文作者:旺德,阿里云数据库高级开发工程师 事务的概念在这篇文章中描述过,在分布式系统中,读写位于多个节点的数据,如果依旧想保证ACID特性,就必须实现分布式事务.而其实现关键则是适当的提交协议,目前 ...

  5. mysql三阶段提交实现_基于两阶段提交的分布式事务实现(UP-2PC)

    引言:分布式事务是分布式数据库的基础性功能,在2017年上海MySQL嘉年华(IMG)和中国数据库大会(DTCC2018)中作者都对银联UPSQL Proxy的分布式事务做了简要介绍,受限于交流形式难 ...

  6. 分布式事务解决方案之2PC(两阶段提交)入门简介

    什么是2PC?       2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase).提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提 ...

  7. 关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究[转]

    1.XA XA是由X/Open组织提出的分布式事务的规范.XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接 ...

  8. 关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究

    本文原文连接: http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的分布式事务的规 ...

  9. 关于分布式事务 两阶段提交 一阶段提交 Best Efforts 1PC模式和事务补偿机制的研究

    本文原文连接: http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的分布式事务的规 ...

最新文章

  1. 跟着柴毛毛学Spring(3)——简化Bean的配置
  2. Linux监听请求到达时间,4: zabbix5.0自动发现网站域名并监控访问状态和请求时间...
  3. wfs.js windows用法
  4. CRM WEB UI 04明细界面添加按钮
  5. node mysql 事件循环_NodeJs 的 Event loop 事件循环机制详解
  6. Xcode Cocoa程序MessageBox 弹框
  7. JS制作支付倒计时页面
  8. Oracle分析函数-keep(dense_rank first/last)
  9. plsql如何执行单个语句_在单个try-with-resources语句中仔细指定多个资源
  10. Python小练习2:pandas.Dataframe使用方法示例demo
  11. java 配置jmstemplate_Spring JMSTemplate 与 JMS 原生API比较
  12. Spring入门示例及相关概念介绍
  13. linux man使用方法 和centos安装中文man包
  14. 【LeetCode 剑指offer刷题】查找与排序题11:Sort Colors
  15. 性能测试流程及数据分析
  16. CentOS7 建立静态 IP(eth0)
  17. Ubuntu14.10 更新源,现在很多源已经停用了建议使用ubuntu15以上
  18. 穿山甲——流量变现新猛兽
  19. 谷歌浏览器登录不了账号_腾讯qq无故冻结账号什么情况?qq号为什么登录不了方法官方回应...
  20. Gdk-CRITICAL **: IA__gdk_drawable_get_size: assertion `GDK_IS_DRAWABLE (drawable)‘ failed

热门文章

  1. C语言原码、反码、补码
  2. python split()函数
  3. ZOJ 3826 Hierarchical Notation 模拟
  4. dede首页调用会员积分和头像代码
  5. 【python】Python的基本数据类型之数字类型与字符串类型
  6. Java定时器的cron设置详解Quartz
  7. 01-10 Linux-bash编程
  8. CI(持续集成)/CD(持续部署)
  9. stm32实验报告心得体会_嵌入式第9次实验报告
  10. win10如何打开摄像头_win10系统如何打开自带游戏?