【问题标题】:Thread safe field value counting线程安全字段值计数
【发布时间】:2011-12-10 16:17:08
【问题描述】:

有没有办法在 django 中做这样的事情?

INSERT INTO t VALUES(1,(SELECT x.c2 FROM t x ORDER BY c2 DESC LIMIT 1)+1,1);

我有一个包含许多字段的模型。并且其中一个字段的值必须根据之前的记录来设置。

目前我通过简单地选择以前的记录来做到这一点。但这很糟糕,而且不是线程安全的。

def save(self, *args, **kw):
    if self.sn is None or self.sn == ns.DEFAULT_GIFT_SN:
        q = Gift.objects.filter(company = self.company).only('id').order_by('-id')
        if q.count():
            last = q[0]
            next_id = int(last.sn) + 1
        else:
            next_id = 1
        self.sn = next_id
    super(Gift, self).save(*args, **kw)

我想……懒喜欢:

def save(self, *args, **kw):
    if self.sn is None or self.sn == ns.DEFAULT_GIFT_SN:
        self.sn = _F('SELECT x.c2 FROM t x ORDER BY c2 DESC LIMIT 1')
    super(Gift, self).save(*args, **kw)

有什么建议吗?

更新(针对塞萨尔):

class Gift(models.Model):
    company = models.ForeignKey(Company)
    certificate = ChainedForeignKey(Certificate,
        chained_field = "company",
        chained_model_field = "company",
        show_all = False,
        auto_choose = True
    )
    gifter = models.ForeignKey(User, null = True, blank = True)
    giftant_first_name = models.CharField(_('first name'), max_length = 30, blank = True)
    giftant_last_name = models.CharField(_('last name'), max_length = 30, blank = True)
    giftant_email = models.EmailField(_('e-mail address'), blank = True)
    giftant_mobile = models.CharField(max_length = 20, blank = True, null = True)
    due_date = models.DateTimeField()
    exp_date = models.DateTimeField()
    sn = models.CharField(max_length = 32, default = ns.DEFAULT_GIFT_SN, editable = False)
    pin_salt = models.CharField(max_length = 5, editable = False, default = gen_salt)
    pin = models.CharField(max_length = 32, null = True, blank = True)
    postcard = models.ForeignKey(Postcard)
    state_status = models.IntegerField(choices = ns.STATE_STATUSES, default = ns.READY_STATE_STATUS)
    delivery_status = models.IntegerField(choices = ns.DELIVERY_STATUSES, default = ns.WAITING_DELIVERY_STATUS)
    payment_status = models.IntegerField(choices = ns.PAYMENT_STATUSES, default = ns.NOT_PAID_PAYMENT_STATUS)
    usage_status = models.IntegerField(choices = ns.USAGE_STATUSES, default = ns.NOT_USED_USAGE_STATUS)
    nominal = models.FloatField(default = 0)
    used_money = models.FloatField(default = 0)
        use_date = models.DateTimeField(blank = True, null = True)
    pub_date = models.DateTimeField(auto_now_add = True)
    cashier = ChainedForeignKey(CompanyUserProfile, blank = True, null = True,
        chained_field = "company",
        chained_model_field = "company",
        show_all = False,
        auto_choose = True
    )

    class Meta:
        unique_together = ('company', 'sn',)

【问题讨论】:

  • 你能展示你的模型吗?
  • 为什么不直接为 'sn' 属性设置一个默认值 1 并在 Gift 的 ID 上返回一个 MAX,按公司过滤?如果 MAX id > 1,则将其增加 1。然后您将返回一个标量值而不是水合对象。
  • @Brandon 我同意你的想法,这样会更好。但这并不能解决线程安全问题。如果两个线程将同时选择 max 而不是更新。它们都将具有相同的值。其实这可以通过加锁来解决,但是我觉得最好能在查询的时候更新值。
  • 据我所知,Django ORM 无法处理并发问题。如果您的应用具有高度事务性,您可能希望通过数据库端的触发器来处理此问题。

标签: django orm django-models thread-safety


【解决方案1】:

我知道while 是邪恶的,但你可以尝试类似的方法:

sn_is_ok = False
while not sn_is_ok:
    last_id = MyModel.objects.latest('id').id
    try:
        self.sn = last_id + 1
        self.save()
    Except IntegrityError:
        continue
    else:
        sn_is_ok = True

我认为你不会得到超过 2 个循环。

【讨论】:

    【解决方案2】:

    我猜你可以做三件事,从简单到极端疯狂:

    1. Gift.sn 成为 AutoField,如果您无需为每个公司单独增加值就可以逃脱。
    2. 如果唯一性约束失败,请使用unique_together = ('company', 'sn') 约束并更新礼物实例,使用@Stan 提供的示例,最坏的情况是,如果您有大量并发写入,则每轮只能通过一个。
    3. 如果数据库支持,请编写一些自定义 SQL 以获取对 Gift 模型表的锁定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多