如何在Django模型中处理并发?我不希望记录的更改被另一个读取相同记录的用户覆盖。
答案 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)