MongoDB中的多集合、多文档“事务”

23
我知道MongoDB的本质不支持这种类型的事务,也许永远不会支持。但是,在某种程度上我发现自己确实需要以一种有限制的方式使用它们,因此我想出了以下解决方案,并且我想知道:是否这是最佳实践,是否可以改进?(在我将其应用于我的应用程序之前!)
显然,事务是通过应用程序控制的(在我的情况下,是一个Python Web应用程序)。 为了每个文档(在任何集合中)都添加以下字段来控制事务:
'lock_status': bool (true = locked, false = unlocked),
'data_old': dict (of any old values - current values really - that are being changed),
'data_new': dict (of values replacing the old (current) values - should be an identical list to data_old),
'change_complete': bool (true = the update to this specific document has occurred and was successful),
'transaction_id': ObjectId of the parent transaction

此外,还有一个transaction集合,用于存储记录每个正在进行的事务的文档。它们看起来像这样:

{
    '_id': ObjectId,
    'date_added': datetime,
    'status': bool (true = all changes successful, false = in progress),
    'collections': array of collection names involved in the transaction
}
这里是该流程的逻辑。希望它能够在被中断或以其他方式失败时,能够正确地回滚。
1:设置一个“transaction”文档
2:对于每个受此事务影响的文档:
- 将“lock_status”设置为“true”(将文档从修改中“锁定”) - 将“data_old”和“data_new”设置为其旧值和新值 - 将“change_complete”设置为“false” - 将“transaction_id”设置为刚刚创建的“transaction”文档的ObjectId
3:执行更新。针对每个受影响的文档:
- 用“data_new”值替换该文档中任何受影响的字段 - 将“change_complete”设置为“true”
4:将“transaction”文档的“status”设置为“true”(因为所有数据都已成功修改)
5:针对每个受到事务影响的文档,进行一些清理:
- 删除“data_old”和“data_new”,因为它们不再需要 - 将“lock_status”设置为“false”(以解除文档的锁定)
6:删除步骤1中设置的“transaction”文档(或按建议将其标记为已完成)
我认为这个逻辑在逻辑上可行,如果在任何时候失败,所有数据可以回滚或事务可以继续(取决于您想要做什么)。显然,所有回滚/恢复等操作都是由应用程序执行而不是由数据库执行,通过使用具有transaction_id的transaction文档和其他集合中的文档进行。是否有我忽视或忽略的逻辑上的显著错误?是否有更有效的方法(例如,从数据库中少写/读)?
3个回答

13
作为对MongoDB多文档提交的通用响应,可以执行两阶段提交。手册中已经对此进行了相当详细的说明(请参见:http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/)。
手册建议采用的模式简要如下:
  • 设置一个单独的transactions集合,其中包括目标文档源文档状态(事务的状态)
  • 创建新的事务对象,并将initial作为state
  • 开始进行事务并将state更新为pending
  • 将事务应用于两个文档(目标、源)
  • 将事务状态更新为committed
  • 使用find方法确定文档是否反映了事务状态,如果正常,将事务状态更新为done
此外:
  • 您需要手动处理失败场景(某些操作未按以下描述执行)
  • 您需要手动实现回滚,基本上是通过引入名为state值为canceling来实现
关于您的实现的一些特定说明:
  • 我不建议您向源/目标文档添加lock_statusdata_olddata_new等字段。这些应该是事务的属性,而不是文档本身的属性。
  • 为了概括目标/源文档的概念,我认为您可以使用DBrefhttp://www.mongodb.org/display/DOCS/Database+References
  • 我不喜欢在完成交易文档后删除它们的想法。将状态设置为已完成似乎是更好的想法,因为这样可以稍后进行调试并找出进行了哪些交易。我相信您也不会因此耗尽磁盘空间(而且也有解决方案)。
  • 在您的模型中,如何确保一切都按预期更改了?您是否以某种方式检查更改?

  • 1
    “DBrefs”这个建议很好。但是,这种两阶段提交的方法是否适用于多个文档(而不会变得混乱)?在我的情况下,我需要同时更新一个集合中的1个文档和另一个集合中的n个文档(数量不确定)。 - johneth
    1
    我认为您也可以添加DBRef数组。 - jsalonen
    1
    如果一个更新失败了,但你仍然将change_complete更新为true,该怎么办?你如何确保这种情况不会发生? - jsalonen
    如果更新失败,它(应用程序)将根据“data_old”(仍然存在)开始回滚先前成功修改的更新。 - johneth
    1
    好的。如果由于任何原因应用程序在事务设置为完成后未能将“lock_status”更新为“false”,该怎么办?也就是说,如何确保只有当前事务的文档被保留为锁定状态?即使事务成功,它们也可能会让您无法进行进一步的事务和更新,从而导致文档被锁定。 - jsalonen
    显示剩余2条评论

    6

    MongoDB 4.0增加了对多文档ACID事务的支持。

    Java示例:

    try (ClientSession clientSession = client.startSession()) {
       clientSession.startTransaction();
       collection.insertOne(clientSession, docOne);
       collection.insertOne(clientSession, docTwo);
       clientSession.commitTransaction();
    }
    

    请注意,此方法适用于副本集。您仍然可以使用单个节点创建副本集,并在本地计算机上运行。


    1
    只有在复制集中才能正常工作。对于单个实例,您将收到错误提示。 - Sharadr

    1

    MongoDB 4.0正在添加(多集合)多文档事务:link


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接