我正在制作一个Django网络应用程序,它允许用户在通过最终POST将数据库提交到数据库(或还原)之前,在一系列GET / POST上构建一组更改。我必须保持更新与任何并发数据库用户隔离,直到它们被确认(这是一个配置前端),排除在每次POST后提交。
我首选的解决方案是使用每会话事务。这样可以记住在其所属的数据库中记住更改内容(以及它如何影响后续查询)以及实现提交/回滚的所有问题。死锁和长期锁定不是问题,因为由于外部限制,任何时候只能有一个用户配置系统,并且它们表现良好。
但是,我找不到关于设置Django的ORM以使用这种事务模型的文档。我把一个最小的猴子补丁(ew!)放在一起解决问题,但不喜欢这样一个脆弱的解决方案。有没有其他人这样做过?我在某个地方错过了一些文档吗?
(我的Django版本是1.0.2 Final,我使用的是Oracle数据库。)
答案 0 :(得分:8)
多个并发的会话规模事务通常会导致死锁或更糟(更糟糕的是= =活锁,长时间延迟而另一个会话持有锁。)
这种设计不是最好的政策,这就是为什么Django不鼓励它。
以下是更好的解决方案。
设计记录用户更改的 Memento 类。这可以是其表单输入的保存副本。如果状态更改很复杂,您可能需要记录其他信息。否则,表单输入的副本可能就足够了。
在会话中累积 Memento 对象的顺序。请注意,事务中的每个步骤都将涉及从数据中获取并进行验证,以查看纪念链是否仍然“有效”。有时他们不会工作,因为其他人在这一系列纪念品中改变了一些东西。现在怎么办?
当你提出'准备提交?'页面,你重播了 Mementos 的序列,并且非常确定它们会起作用。当提交“提交”时,您必须最后一次重播 Mementos ,希望它们仍然可以继续工作。如果他们这样做,那很好。如果他们不这样做,有人改变了一些东西,你又回到了第2步:现在怎么办?
这似乎很复杂。
是的,确实如此。然而,它没有任何锁定,允许起泡速度和很少的死锁机会。该事务仅限于“提交”视图功能,该功能实际上将 Mementos 的序列应用于数据库,保存结果,并进行最终提交以结束事务。
另一种选择 - 当用户在n-1步骤中走出快速喝咖啡时保持锁定是不可行的。
有关 Memento 的详细信息,请参阅this。
答案 1 :(得分:2)
如果其他人遇到与我完全相同的问题(我希望不是),这是我的monkeypatch。它是脆弱和丑陋的,并且改变了私人方法,但幸运的是它很小。除非你真的需要,否则请不要使用它。正如其他人所提到的,任何使用它的应用程序都会有效地防止多个用户同时进行更新,从而导致死锁。 (在我的应用程序中,可能有许多读者,但故意排除多个并发更新。)
我有一个“用户”对象,它持久存在于用户会话中,并包含一个持久连接对象。当我验证特定HTTP交互是会话的一部分时,我还将用户对象存储在django.db.connection上,这是线程本地的。
def monkeyPatchDjangoDBConnection():
import django.db
def validConnection():
if django.db.connection.connection is None:
django.db.connection.connection = django.db.connection.user.connection
return True
def close():
django.db.connection.connection = None
django.db.connection._valid_connection = validConnection
django.db.connection.close = close
monkeyPatchDBConnection()
def setUserOnThisThread(user):
import django.db
django.db.connection.user = user
在使用@login_required注释的任何方法的开头自动调用此最后一个,因此99%的代码与此hack的细节无关。
答案 2 :(得分:2)
我想出了类似于Memento模式的东西,但不同的是我觉得它有张贴。当用户启动编辑会话时,我将目标对象复制到数据库中的临时对象。所有后续编辑操作都会影响副本。我没有在每次更改时将对象状态保存在 memento 中,而是存储操作对象。当我将操作应用于对象时,它返回我存储的逆操作。
保存操作比mementos便宜得多,因为可以使用一些小数据项来描述操作,而正在编辑的对象要大得多。此外,我按照我的方式应用操作并保存undos,以便db中的临时文件始终对应于用户浏览器中的版本。我永远不必重播一系列变化;临时总是只有一个操作,远离下一个版本。
要实现“撤消”,我会从堆栈中弹出最后一个撤消对象(通过从db中检索临时对象的最新操作)将其应用于临时对象并返回已转换的临时对象。如果我关心实现重做,我也可以将结果操作推送到重做堆栈。
要实现“保存更改”,即提交,我取消激活原始对象并为其设置时间戳并激活临时对象。
要实现“取消”,即回滚,我什么都不做!当然,我可以删除临时文件,因为编辑会话结束后用户无法检索它,但我喜欢保留已取消的编辑会话,以便我可以在使用cron作业清除它们之前对它们运行统计信息