处理model.save()中的竞争条件

Sha*_*hin 17 python database django django-models race-condition

如何在模型的save()方法中处理可能的竞争条件?

例如,以下示例实现具有相关项的有序列表的模型.创建新项目时,当前列表大小用作其位置.

据我所知,如果同时创建多个项目,这可能会出错.

class OrderedList(models.Model):
    # ....
    @property
    def item_count(self):
        return self.item_set.count()

class Item(models.Model):
    # ...
    name   = models.CharField(max_length=100)
    parent = models.ForeignKey(OrderedList)
    position = models.IntegerField()
    class Meta:
        unique_together = (('parent','position'), ('parent', 'name'))

    def save(self, *args, **kwargs):
        if not self.id:
            # use item count as next position number
            self.position = parent.item_count
        super(Item, self).save(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

我遇到过@transactions.commit_on_success()但这似乎只适用于视图.即使它确实适用于模型方法,我仍然不知道如何正确处理失败的事务.

我当前正在处理它,但它感觉更像是一个黑客而不是一个解决方案

def save(self, *args, **kwargs):
    while not self.id:
        try:
            self.position = self.parent.item_count
            super(Item, self).save(*args, **kwargs)
        except IntegrityError:
            # chill out, then try again
            time.sleep(0.5)
Run Code Online (Sandbox Code Playgroud)

有什么建议?

更新:

上述解决方案的另一个问题是,while如果IntegrityErrorname冲突(或该问题的任何其他唯一字段)引起,则循环将永远不会结束.

为了记录,这是我到目前为止所做的我似乎做的事情:

def save(self, *args, **kwargs):   
    # for object update, do the usual save     
    if self.id: 
        super(Step, self).save(*args, **kwargs)
        return

    # for object creation, assign a unique position
    while not self.id:
        try:
            self.position = self.parent.item_count
            super(Step, self).save(*args, **kwargs)
        except IntegrityError:
            try:
                rival = self.parent.item_set.get(position=self.position)
            except ObjectDoesNotExist: # not a conflict on "position"
                raise IntegrityError
            else:
                sleep(random.uniform(0.5, 1)) # chill out, then try again
Run Code Online (Sandbox Code Playgroud)

Ale*_*lli 15

它可能感觉像是一个黑客,但对我来说,它看起来像一个合理的,合理的"乐观并发"方法的实现 - 尝试做任何事情,检测由竞争条件引起的冲突,如果发生,稍后重试.有些数据库系统地使用它而不是锁定,它可以带来更好的性能,除非在大量写入负载下的系统(在现实生活中很少见).

我非常喜欢它,因为我认为这是Hopper原则的一般情况:"很容易请求宽恕而非许可",这在编程中广泛应用(特别是但不仅限于Python) - Hopper通常被认为是毕竟,Cobol ;-).

一个改进我建议是等待一个随机的时间-避免"元竞态条件",其中两个进程试图在同一时间,发现两者的冲突,都重试再次在同一时间,导致"饥饿". time.sleep(random.uniform(0.1, 0.6))等等应该足够了.

更精确的改进是如果更多冲突得到满足则延长预期等待 - 这就是TCP/IP中所谓的"指数退避"(您不必以指数方式延长事物,即每个乘数> 1)当然,时间,但这种方法有很好的数学属性).这只是必要的,以限制对问题非常写加载系统(其中在试图写入多个冲突经常发生),它可能会不值得你的具体情况.