Django阻止并发创建对象

时间:2019-03-13 18:37:59

标签: django concurrency

型号:

class CouponUsage(models.Model):
    coupon = models.ForeignKey('Coupon', on_delete=models.CASCADE, related_name="usage")
    date = models.DateTimeField(auto_now_add=True)    

class Coupon(models.Model):
    name = models.CharField(max_length=255)
    capacity = models.IntegerField()

    @property
    def remaining(self):
        usage = self.usage.all().count()
        return self.capacity - usage

观看次数:

def use_coupon(request):
    coupon = Coupon.objects.get(condition)

    if coupon.remaining > 0:
        # do something

我不知道如何处理上面代码中的并发问题,我相信一个可能的错误是,当视图中的if子句正在执行时,可以创建另一个CouponUsage对象。 我该如何处理呢?

当在视图的if子句中时,如何防止创建CouponUsage对象

1 个答案:

答案 0 :(得分:0)

执行此操作的一种方法是依赖数据库完整性检查和事务。假设您的容量必须始终在[0,+ infinity]范围内,则可以将Coupon模型更改为使用PositiveIntegerField而不是IntegerField

class Coupon(models.Model):
    name = models.CharField(max_length=255)
    capacity = models.PositiveIntegerField()

然后,您需要在每次创建Coupon时更新CouponUsage的容量。您可以覆盖save()方法以反映此更改:

from django.db import models, transaction

class CouponUsage(models.Model):
    coupon = models.ForeignKey('Coupon', on_delete=models.CASCADE, related_name="usage")
    date = models.DateTimeField(auto_now_add=True) 

    @transaction.atomic()
    def save(self, ...):  # Arguments missing
        if not self.pk:  # This is an insert, you may want to raise an error otherwise
            self.coupon.capacity = models.F('capacity') - 1  # The magic is here, this is executed at the database level so no problem with old in memory values
            self.coupon.save()
        super().save(...)

现在,无论何时创建CuponUsage,您都将更新关联的Coupon实例的容量。这里的关键是,不是从数据库中将值读取到python的内存中,而是先进行更新然后保存,否则可能导致结果不一致,而是使用an F expression在数据库级别对capacity进行更新。这样可以保证没有两个事务使用相同的值。

现在,请注意,通过使用PositiveInteger字段而不是IntegerField,数据库还将保证capacity不能低于0。因此,如果您现在尝试创建{{ 1}}实例,使得CuponUsage容量将为负值,则会出现异常,从而阻止创建此类Cupon

您现在需要通过执行以下操作来在代码中利用此优势:

CuponUsage

如果在获取优惠券时您需要做可能会失败的事情,并且在这种情况下需要“还原”使用情况,则可以将整个def use_coupon(request): coupon = Coupon.objects.get(condition) try: usage = CuponUsage.objects.create(coupon=coupon) # Do whatever you want here, you already 'consumed' a coupon except IntegrityError: # Check for the specific exception # Sorry no capacity left pass 函数包含在交易中。 / p>