我意识到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
文档和具有该transaction_id的其他集合中的文档来执行。
这个逻辑中有没有我遗漏或忽略的明显错误?有没有更有效的方法(例如,减少对数据库的写入/读取)
作为一种通用响应,MongoDB上的多文档提交可以作为两个阶段的提交来执行,这在手册中有大量的文档记录(请参阅:http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/)。
手册建议的模式简要如下:
- 设置一个单独的
transactions
集合,其中包括目标文档、源文档值和状态(事务) - 以
initial
作为state
创建新的事务对象 - 开始进行事务处理并将
state
更新为pending
- 将交易记录应用于两个文档(目标、源)
- 将事务状态更新为
committed
- 使用find确定文档是否反映事务状态,如果可以,则将事务状态更新为
done
此外:
- 您需要手动处理失败场景(如下所述,某些事情没有发生)
- 您需要手动实现回滚,基本上是通过引入名称
state
值canceling
实现的一些具体注意事项:
- 我建议您不要在源/目标文档中添加
lock_status
、data_old
、data_new
等字段。这些应该是事务的属性,而不是文档本身 - 为了概括目标/源文档的概念,我认为您可以使用
DBref
s:http://www.mongodb.org/display/DOCS/Database+参考文献 - 我不喜欢在交易文档完成后删除它们的想法。将state设置为
done
似乎是一个更好的主意,因为这样可以让您稍后调试并了解执行了什么类型的事务。我确信您也不会耗尽磁盘空间(对此也有解决方案) - 在您的模型中,如何保证一切都按预期进行了更改?你是否以某种方式检查了这些变化
MongoDB 4.0增加了对多文档ACID事务的支持。
Java示例:
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
collection.insertOne(clientSession, docOne);
collection.insertOne(clientSession, docTwo);
clientSession.commitTransaction();
}
注意,它适用于副本集。您仍然可以拥有一个具有一个节点的复制副本集,并在本地计算机上运行它。
- https://stackoverflow.com/a/51396785/4587961
- https://docs.mongodb.com/manual/tutorial/deploy-replica-set-for-testing/
MongoDB 4.0正在添加(多集合)多文档事务:链接