在模型的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)

我遇到过@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)

有什么建议吗?

更新:

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

为了记录在案,以下是我到目前为止所拥有的似乎能满足我需要的东西:

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

推荐答案

它可能对您来说像是一次黑客攻击,但对我来说,它看起来像是"乐观并发"方法的合法、合理的实现--try 做任何事情,检测由竞争条件引起的冲突,如果发生冲突,请稍后重试.一些数据库系统地使用它而不是锁定,并且它可以带来更好的性能,除非在写负载为lot的系统下(这在现实生活中非常罕见).

我非常喜欢它,因为我认为它是Hopper原则的一般情况:"请求原谅比请求许可更容易",这一原则在编程中应用广泛(特别是但不只是在Python中--Hopper通常被归功于Cobol;-).

我建议的一个改进是等待random段时间——避免出现"元竞争条件",即两个进程同时try ,都发现冲突,并且都同时重试again次,从而导致"饥饿".time.sleep(random.uniform(0.1, 0.6))美元左右就足够了.

A more refined improvement is to lengthen the expected wait if more conflicts are met -- this is what is known as "exponential backoff" in TCP/IP (you wouldn't have to lengthen things exponentially, i.e. by a constant multiplier > 1 each time, of course, but that approach has nice mathematical properties). It's only warranted to limit problems for very write-loaded systems (where multiple conflicts during attempted writes happen quite often) and it may likely not be worth it in your specific case.

Database相关问答推荐

如何自动更新不同的数据库?

KUST查询指定时间跨度内里程表&值的差值,并将其滚动到0

Oracle批量数据处理

您是否遇到过 SQL Server 因为引用了太多表而无法执行的查询?

术语 SSTable 和 LSM Tree 有什么区别

实体关系图. IS A 关系如何转换为table表?

什么是 Scalar标量查询?

在 MySQL 中查看表以进行更改?

PostgreSQL - 将每个表转储到不同的文件中

将数据库表用作作业(job)队列的最佳方式是什么?

连接池策略效果怎样?

如何在 MySQL 中强制执行唯一约束?

MySQL是否允许使用点创建数据库?

我可以用 JPA 命名我的约束吗?

部分依赖(数据库)

使用批处理文件执行一组 SQL 查询?

从数据库中获取事件

使用 Django 的复合/复合主/唯一键

SQLite 数据库方案作为实体关系模型

我可以在 /sdcard 上下载 SQLite 数据库并从我的 Android 应用程序访问它吗?