代码之家  ›  专栏  ›  技术社区  ›  Shawn Chin

处理竞争条件模型.保存()

  •  16
  • Shawn Chin  · 技术社区  · 14 年前

    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)
    

    更新:

    上述解决方案的另一个问题是 while 循环永远不会结束,如果 IntegrityError 是由 name

    作为记录,以下是我目前所拥有的,似乎能满足我的需要:

    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
    
    3 回复  |  直到 13 年前
        1
  •  16
  •   Alex Martelli    14 年前

    可能吧 感觉 对你来说就像是黑客攻击,但对我来说,它看起来像是“乐观并发”方法的合法、合理的实现——尝试做任何事情,检测由竞争条件引起的冲突,如果发生冲突,请稍后重试。一些数据库系统地使用它而不是锁定,并且它可以导致更好的性能,除非在 写负载(在现实生活中很少见)。

    我建议的一个改进是等一会儿 随机的 时间量—避免“元竞争条件”,即两个进程同时尝试,都找到冲突,然后都重试 再一次 同时,导致“饥饿”。 time.sleep(random.uniform(0.1, 0.6))

    一个更精细的改进是,如果遇到更多冲突,则延长预期的等待时间——这就是TCP/IP中所称的“指数退避”(您不必以指数方式延长时间,即通过一个常数乘数>当然,每次都是1,但这种方法有很好的数学特性)。它只允许限制 非常 写加载的系统(在尝试写的过程中经常发生多个冲突),在您的特定情况下可能不值得这样做。

        3
  •  0
  •   Bastian    13 年前

    self.position = self.parent.item_count
    

    具有

    self.position = self.parent.latest('position').position
    

    只是为了确保我处理的是最新的职位编号(在我的情况下,由于一些保留的未使用的职位,该编号可能不是item\u count)