代码之家  ›  专栏  ›  技术社区  ›  Alice Purcell

Django中的每会话事务

  •  8
  • Alice Purcell  · 技术社区  · 15 年前

    我正在制作一个django web应用程序,它允许用户在一系列的get/post上构建一组更改,然后将它们提交到数据库(或恢复到最后一个post)。我必须将更新与任何并发数据库用户隔离,直到他们得到确认(这是一个配置前端),排除在每次发布之后提交。

    我的首选解决方案是使用每会话事务。这将保留所有的问题,即记住更改的内容(以及它如何影响后续查询),以及在它所属的数据库中实现提交/回滚。死锁和长期持有的锁不是问题,因为由于外部约束,在任何时候只能有一个用户配置系统,并且它们的行为良好。

    但是,我找不到有关设置Django的ORM以使用此类事务模型的文档。我把一个最小的猴子补丁(ew!)解决问题,但不喜欢这样脆弱的解决方案。以前有人做过吗?我有没有遗漏一些文件?

    (我的django版本是1.0.2 final,我使用的是Oracle数据库。)

    3 回复  |  直到 15 年前
        1
  •  8
  •   S.Lott    15 年前

    多个、并发、会话级事务通常会导致死锁或更糟(更糟的是==LiveLock,当锁被另一个会话持有时会有较长的延迟。)

    这种设计不是最好的策略,这也是姜戈不鼓励它的原因。

    更好的解决方案如下。

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

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

    3. 当你提出“准备好承诺了吗?”page,您已经重播了 纪念品 他们肯定会成功的。当提交“提交”时,必须重播 纪念品 最后一次,希望他们能继续工作。如果有,很好。如果他们没有,有人改变了一些事情,而你又回到了第二步:现在怎么办?

    这似乎很复杂。

    是的,是的。然而,它不持有任何锁,允许起泡的速度和很少的机会死锁。事务仅限于“提交”视图函数,该函数实际应用 纪念品 到数据库,保存结果,并执行最终提交以结束事务。

    另一种方法——在用户走出去喝一杯咖啡的时候,在n-1的步骤中保持锁——是不可行的。

    有关更多信息 纪念品 this .

        2
  •  2
  •   Alice Purcell    15 年前

    如果有人和我有同样的问题(我希望没有),这是我的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_注释的任何方法开始时自动调用的,因此99%的代码与此黑客的细节是隔离的。

        3
  •  2
  •   Aryeh Leib Taurog    15 年前

    我想出了一个类似的纪念品模式,但不同的足够我认为它有张贴。当用户开始编辑会话时,我将目标对象复制到数据库中的临时对象。所有后续编辑操作都会影响副本。而不是将对象状态保存在 纪念品 每次换车时,我都会存储 操作 物体。当我对一个对象应用一个操作时,它返回 我存储的操作。

    对我来说,保存操作要比记忆便宜得多,因为这些操作可以用一些小的数据项来描述,而正在编辑的对象要大得多。另外,我在执行操作时会应用这些操作,并保存撤消操作,这样数据库中的临时文件总是与用户浏览器中的版本相对应。我从不需要重放一组更改;临时操作总是离下一个版本只有一个操作。

    为了实现“撤消”,我从堆栈中弹出最后一个撤消对象(实际上是——通过从数据库中检索临时对象的最新操作),将其应用于临时对象并返回转换后的临时对象。如果我想实现重做,我也可以将结果操作推送到重做堆栈上。

    要实现“保存更改”,即提交,我取消激活并在原始对象上加盖时间戳,并在其位置激活临时对象。

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