Django模型中的并发控制

时间:2009-10-29 17:19:12

标签: python django concurrency

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

3 个答案:

答案 0 :(得分:22)

简短的回答,这真的不是呈现的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属性,这些模型都不是完全可行的。

答案 1 :(得分:11)

我认为“保留版本号或时间戳”不起作用。

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

答案 2 :(得分:2)

我同意Joe Holloway的介绍性解释。

我想提供一个相对于他的答案最后部分的工作片段(“就Django而言,乐观并发控制可以通过覆盖模型类上的save方法来实现......”)

您可以将以下类用作您自己模型的祖先

如果您在db事务中(例如,在外部作用域中使用transaction.atomic),则以下python语句是安全且一致的

在实践中,通过一次单击,语句过滤器+更新在记录上提供了一种test_and_set:它们验证版本并获取行上的隐式数据库级锁定。 因此,以下“保存”能够更新记录的字段,确保它是唯一在该模型实例上运行的会话。 最后一次提交(例如,在transaction.atomic中由__exit__自动执行)释放行上的隐式数据库级锁定

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