2009-06-23 39 views
7

我正在做一个Django的Web应用程序,它允许用户建立一系列GET/POSTs之前,通过一个最终的POST提交到数据库(或恢复)一系列的变化。我必须保持更新与任何并发数据库用户隔离,直到他们被确认(这是一个配置前端),在每次POST后排除提交。在Django每会话事务

我的首选解决方案是使用每会话事务。这样可以在记录它所属的数据库中记录所发生的变化(以及它如何影响后续查询)以及实现提交/回滚的所有问题。死锁和长期锁定不是问题,因为由于外部约束,每次只能有一个用户配置系统,并且它们表现良好。

但是,我找不到有关设置Django的ORM使用这种事务模型的文档。我一起扔了一个最小的猴子补丁(ew!)来解决问题,但不喜欢这种脆弱的解决方案。有没有其他人做过?我在某处错过了一些文档吗?

(我的Django的版本是1.0.2决赛,和我使用的是Oracle数据库。)

回答

9

多个并发会话大规模交易通常会导致死锁或更糟(更糟==活锁,长延迟,而锁由另一个会话举行。)

这种设计不是最好的政策,这就是为什么Django不鼓励它。

更好的解决方案如下。

  1. 设计一个备忘录类,记录用户的变化。这可能是其表单输入的保存副本。如果状态更改很复杂,则可能需要记录其他信息。否则,表单输入的副本可能就足够了。

  2. 累积序列纪念品对象在他们的会话中。请注意,交易中的每个步骤都将涉及从数据中提取和验证以查看这条记忆链是否仍然“有效”。有时他们不会工作,因为别人在这条链条上改变了一些东西。现在怎么办?

  3. 当您提交'准备提交?'时页面,你已经重播了纪念品的序列,并且非常确定它们会起作用。当提交“提交”时,你必须最后一次重放纪念品,希望他们仍然能够工作。如果他们这样做,很好。如果他们不这样做,某人改变了一些事情,而你又回到了第二步:现在呢?

这似乎很复杂。

是的,它的确如此。然而,它并没有锁定任何锁定,允许速度很快而且很少有机会发生死锁。该交易仅限于“提交”视图功能,该功能实际上将数据序列Mementos应用于数据库,保存结果并做最后的提交以结束交易。

另一种方法 - 在用户在步骤n-1中步出一杯快速咖啡时锁住锁 - 是行不通的。

欲了解更多关于纪念品,请参阅this

+0

死锁和用户出去喝咖啡并不是问题(将有一个控制器,并且通过设计,整个更新在单个锁下完成)。 纠正我,如果我错了,但纪实不会与ORM工作,他们会吗? – 2009-06-23 17:37:32

1

如果任何人有过完全一样的问题,因为我(我希望不是),这里是我的猴补丁。这是脆弱和丑陋的,并改变私人方法,但幸好它很小。除非你真的需要,否则请不要使用它。正如其他人所提到的,任何使用它的应用程序都会有效地防止多个用户同时进行更新,从而导致死锁。 (在我的应用程序,可能有很多读者,但多个并发更新是故意除外)。

我有一个“用户”的对象,它仍然存在跨用户会话,并包含一个持久连接对象。当我验证特定的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%的我的代码是从这个技巧的细节绝缘。

2

我想出了类似备忘录模式,但不多,所以我认为它担负着发布不同的东西。当用户开始编辑会话时,我将目标对象复制到数据库中的临时对象。所有后续的编辑操作都会影响重复。在每次更改时,我都不会将对象状态保存在备忘录中,而是存储操作对象。当我将操作应用于对象时,它会返回我存储的反向操作。

保存操作对我来说比纪念品便宜得多,因为操作可以通过一些小的数据项进行描述,同时正在编辑的对象要大得多。另外,我会按照我的操作来应用这些操作并保存撤销信息,以便db中的临时信息始终与用户浏览器中的版本相对应。我从来不需要重播一系列变化;从下一个版本开始临时只有一个操作。

要实现“撤销”,我弹出一次撤销的对象从堆栈(因为它是 - 通过检索从DB临时对象最近的操作),它适用于临时性,并返回转化暂时的。如果我关心实现重做,我也可以将结果操作推送到重做堆栈上。

要实现“保存更改”,即承诺,我去激活和时间标记的原始对象并启动临时代替它。

要实现“取消”,即回滚,我什么也不做!当然,我可以删除临时文件,因为在编辑会话结束后用户无法检索它,但是我希望保留已取消的编辑会话,这样我就可以在用cron作业清除它们之前运行统计信息。