执行两阶段提交
一、概要
本文档提供了多文档更新或“多文档事物处理”采用两阶段模式提交的方式将数据写入到多个文档。此外,你可以扩展这一过程,以提供回滚一样的功能。
二、背景
MongoDB数据库单一的文档操作总是原子的;然而,操作涉及多个文档,这些文件通常被称为“多文件事物处理”,不是原子的。由于文档可以是相当复杂的,包含多个“嵌套”的文件,单个文档原子为许多实用的用例的提供了必要的支持。
即使单文档的原子操作力,也存在需要多文档处理情况。当执行一个顺序操作组成的事务,某些问题发生,如:
原子性:如果一个操作失败,先前的操作在事务必须“回滚”到先前的状态(例如“nothing,”“all 或者 nothing”)。
一致性:如果一个主要失败(例如网络、硬件)中断事务,数据库必须能够恢复一致的状态。
对于需要多文档事物处理的情况下,可以实现两阶段提交您的应用程序提供这类多文档的更新支持。使用两阶段提交保证了数据的一致性,在出错的情况下,与此前的事物状态是可以恢复的。在该过程中,然而,文档可以表示待处理数据和状态。
请注意
在与MongoDB中因为只有单文档操作是原子,两阶段提交只能提供transaction-like语义。在两阶段提交或回滚时应用程序有可能返回中间点的中间数据。
三、模式
概述
考虑这样一个场景,您想从帐户转帐资金到账户B .在关系数据库系统中,你可以从B减去款项然后添加款项到A在一个multi-statement事务。在MongoDB中,可以模拟一个两阶段提交达到类似的结果。
本教程中的示例使用以下两个集合:
一个集合名为accounts存储账号信息
一个集合名为transactions存储资金转账处理的信息
初始化源和目标帐户
在accounts集合中插入一个账号A的文档和一个账号B的文档。
db.accounts.insert(
[
{ _id: "A", balance: 1000, pendingTransactions: [] },
{ _id: "B", balance: 1000, pendingTransactions: [] }
]
)
操作返回反映操作状态的BulkWriteResult()对象。一旦插入成功,则BulkWriteResult()的nInserted设置为2。
初始化转移记录
对于每个基金转移到执行,插入transactions集合一个转账信息的文档。文档包含以下字段:
source和destination字段,引用account集合中的_id字段。
value字段,指定转账的金额,平衡源账号和目标账号
state字段,它反映了当前转账的状态。state字段的值可以为initial, pending, applied, done, canceling, 和canceled。
lastModified字段,它反映最后修改的日期。
开始从账号A转账100到账号B,插入transactions集合一个转账处理信息的文档, state为“initial”,lastModified字段值设为当前日期。
db.transactions.insert(
{ _id: 1, source: "A", destination: "B", value: 100, state: "initial", lastModified: new Date() }
)
操作返回该操作状态的WriteResult()对象。一旦插入成功,该WriteResult()对象nInserted设置为1。
账户之间转移资金使用两阶段提交
四、从故障恢复方案
上面交易过程中最重要的部分不是典型的例子,而是可能来自不同的故障情况恢复时,交易未成功完成。本节介绍了可能出现的故障的概述,并提供步骤,从这类事件中恢复过来。
恢复操作
两阶段提交模式允许应用程序按运行的顺序恢复交易,并得出一个一致的状态。运行在应用程序启动恢复操作,并有可能定期,捕捉任何未完成的事务。
达成一致的状态所需的时间取决于应用程序需要多久恢复每一笔交易。
下面的恢复过程使用上次更改日期的未决事务是否需要恢复的指标;具体地说,如果未决或施加交易尚未在最后30分钟更新时,程序确定,这些交易需要恢复。您可以使用不同的条件来做出此决定。
交易挂起状态
从步骤之后发生的故障中恢复,但在此之前“交易使用更新状态。”这一步,从交易集合恢复挂起的事务,检索“更新交易状态为挂起。”:
var dateThreshold = new Date(); dateThreshold.setMinutes(dateThreshold.getMinutes() - 30); var t = db.transactions.findOne( { state: "pending", lastModified: { $lt: dateThreshold } } );
从恢复“更新未决交易的两个帐户名单。”
回滚操作
在某些情况下,你可能需要“回滚”或撤消交易;例如,如果应用程序需要“取消”的交易或者账户不存在或存在交易过程中止。
在应用状态的事物
经过“更新交易状态使用。”这一步,你不应该回滚事务。相反,完成该交易,并创建一个新的事务的源和目标字段切换值进行反向交易。
挂起状态的交易
1.更新交易状态取消。
从等候到取消更新事务状态。
db.transactions.update( { _id: t._id, state: "pending" }, { $set: { state: "canceling" }, $currentDate: { lastModified: true } } )
一旦成功更新,该方法返回nMatched和n修改一个WriteResult()对象设置为1。
2.撤消对两个帐户的交易。
要撤消该交易对两个帐户,扭转事务T,如果该交易得到了应用。在更新条件,包括条件pendingTransactions:以更新仅在未决事务已应用于该帐户t._id。
更新目标帐户,从它的平衡减去交易值,并从pendingTransactions阵列除去事务_id。
db.accounts.update( { _id: t.destination, pendingTransactions: t._id }, { $inc: { balance: -t.value }, $pull: { pendingTransactions: t._id } } )