【问题标题】:Concurrency control in Django modelDjango模型中的并发控制
【发布时间】:2010-12-11 08:14:40
【问题描述】:

如何在 Django 模型中处理并发?我不希望读取相同记录的其他用户覆盖对记录的更改。

【问题讨论】:

标签: python django django-models concurrency


【解决方案1】:

简短的回答,这确实不是提出的 Django 问题。

并发控制通常作为技术问题提出,但在许多方面是功能需求问题。您希望/需要您的应用程序如何工作?在我们知道这一点之前,很难给出任何特定于 Django 的建议。

但是,我觉得漫无目的,所以就这样吧……

在需要并发控制时,我倾向于问自己两个问题:

  • 两个用户需要同时修改同一记录的可能性有多大?
  • 如果用户对记录的修改丢失,对用户有何影响?

如果发生冲突的可能性相对较高,或者丢失修改的影响很大,那么您可能正在考虑某种形式的悲观锁定。在悲观方案中,每个用户必须在打开记录进行修改之前获得一个逻辑锁。

悲观锁定具有很多复杂性。你必须同步对锁的访问,考虑容错,锁过期,锁是否可以被超级用户覆盖,用户可以看到谁拥有锁等等。

在 Django 中,这可以通过单独的 Lock 模型或锁定记录上的某种“锁定用户”外键来实现。使用锁表在存储获取锁的时间、用户、注释等方面为您提供了更多的灵活性。如果您需要一个可用于锁定任何类型记录的通用锁表,请查看django.contrib.contenttypes framework,但很快这会演变为抽象宇航员综合症。

如果不太可能发生冲突或丢失的修改很容易重新创建,那么您可以在功能上摆脱乐观并发技术。这种技术简单且易于实施。本质上,您只需跟踪版本号或修改时间戳,并拒绝您检测到的任何异常修改。

从功能设计的角度来看,您只需考虑这些并发修改错误是如何呈现给您的用户的。

就 Django 而言,乐观并发控制可以通过覆盖模型类的 save 方法来实现...

def save(self, *args, **kwargs):
    if self.version != self.read_current_version():
        raise ConcurrentModificationError('Ooops!!!!')
    super(MyModel, self).save(*args, **kwargs)

当然,要使这些并发机制中的任何一个变得健壮,您必须考虑transactional control。如果您不能保证事务的 ACID 属性,那么这两种模型都不完全可行。

【讨论】:

  • 好的,你写的最后一个例子是我想知道的,所以我必须为模型编写自己的保存方法。当我将属性设置为“compareallvalues”到控件时,我来自另一个框架,所以我不知道如何在 Django 中实现它,而且我在入门教程中没有找到任何示例,或者我错过了它。我认为由于 Django 自动执行了在其他框架中由程序员完成的多项任务,因此可以像我首先提到的框架中那样自动执行此任务
  • 是的,我个人认为框架应该避免泛化这些设计模式,因为所有的功能/技术影响。 YMMV...
  • 如下所述,最后一个 sn-p 中的代码已损坏。版本检查和保存方法之间仍然可能出现修改。
  • @julkiewicz 正如我在答案的最后一段中指出的那样,它仅在它在原子数据库事务的上下文中执行时才有效。这在我的代码 sn-p 中没有演示,因为它可能会在更高层的代码中处理。
  • 好的,但是值得注意的一点是,一旦出现死锁,数据库不会自动重新启动它们的事务是很常见的。而上面的代码可能会导致死锁。
【解决方案2】:

我认为“保留版本号或时间戳”行不通。

self.version == self.read_current_version()True 时,版本号仍有可能在您调用super().save() 之前被其他会话修改。

【讨论】:

  • 不锁定有问题的表,这是正确的。但是,有一些装饰器可以使用 Django 模型简化表锁定,这应该可以避免您所指的竞争条件。
【解决方案3】:

我同意介绍性的explanation from Joe Holloway

我想为他回答的最后一部分提供一个有效的 sn-p(“就 Django 而言,可以通过覆盖模型类上的 save 方法来实现乐观并发控制......”)

您可以使用以下类作为您自己模型的祖先。

如果您在数据库事务内部(例如,通过在外部范围内使用 transaction.atomic),则以下 Python 语句是安全且一致的

在实践中,通过单次触发,语句 filter + update 在记录上提供了一种 test_and_set:它们验证版本并获取行上的隐式数据库级锁。

因此,以下“保存”能够更新记录的字段,确保它是对该模型实例进行操作的唯一会话。

最终提交(例如由_exit_在transaction.atomic中自动执行)释放行上的隐式数据库级锁:

class ConcurrentModel(models.Model):
    _change = models.IntegerField(default=0)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        cls = self.__class__
        if self.pk:
            rows = cls.objects.filter(
                pk=self.pk, _change=self._change).update(
                _change=self._change + 1)
            if not rows:
                raise ConcurrentModificationError(cls.__name__, self.pk)
            self._change += 1
        super(ConcurrentModel, self).save(*args, **kwargs)

取自 https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default

【讨论】:

    猜你喜欢
    • 2012-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 1970-01-01
    • 1970-01-01
    • 2016-02-07
    • 2013-08-20
    相关资源
    最近更新 更多