2011-12-10 51 views
2

有什么办法在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) 

有什么建议吗?

UPDATE(塞萨尔):

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',) 
+0

你能展示你的模型吗? –

+0

为什么不为'sn'属性设置默认值1,并在礼品ID上返回一个MAX,由公司过滤?如果MAX id> 1,则将其增加1.然后,您将返回标量值而不是保湿对象。 – Brandon

+0

@Brandon我同意你的想法,这会更好。但是这并没有解决线程安全的问题。如果两个线程将同时选择最大值而不是更新。他们两人将具有相同的价值。实际上,这可以通过锁定来解决,但我认为能够在查询时更新值会更好。 –

回答

0

我想你可以做三件事情,从简单到在极端败坏狂:

  1. Gift.snAutoField,如果你能逃脱,而无需单独增加值每家公司。
  2. 使用unique_together = ('company', 'sn')约束并在唯一性约束失败时更新礼物实例,请使用提供的示例@Stan,以及最糟糕的情况,即如果您有很多并发写入,则每次只会传递一个。
  3. 如果数据库支持Gift模型表,编写一些自定义SQL以获取锁定。
+0

我使用了select_for_update来获取此操作的锁定。 –

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路。