在我的Django应用程序中,我有以下两个模型:
class Event(models.Model):
capacity = models.PositiveSmallIntegerField()
def get_number_of_registered_tickets():
return EventRegistration.objects.filter(event__exact=self).aggregate(total=Coalesce(Sum('number_tickets'), 0))['total']
class EventRegistration(models.Model):
time = models.DateTimeField(auto_now_add=True)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
number_tickets = models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
我在应用程序的多个位置都需要使用方法get_number_of_registered_tickets()
(例如,模板渲染)。因此,我认为将其放入模型中也是有意义的,因为它与之相关,而且我经常听到拥有“胖模型和轻量级视图”是很好的。
我现在的问题是: 为了防止两个人要同时注册事件 ,我必须使用锁定。范例:假设还有一张车票要注册。现在,向人们展示在我的网站上,同时单击“注册”。在不幸的情况下,这两个请求都可能有效,现在我的注册量超出了容量。
我是Django的新手,但阅读文档后,我认为应该select_for_update()
是解决方案,我在这里吗(我使用PostgreSQL,因此应该受支持)?
但是,文档还说,使用select_for_update()
仅在交易中有效。
在自动提交模式下使用
select_for_update()
评估查询集 支持SELECT ... FOR UPDATE
的后端是一个TransactionManagementError
错误,因为行未锁定 这种情况。如果允许,这将促进数据损坏,并且可能 很容易由调用期望在 一笔交易。
我现在的想法是更改模型方法get_number_of_registered_tickets()
并添加 select_for_update():
def get_number_of_registered_tickets():
return EventRegistration.objects.select_for_update().filter(event__exact=self).aggregate(total=Coalesce(Sum('number_tickets'), 0))['total']
现在有不同的问题:
select_for_update()
是解决我的问题的正确方法吗?get_number_of_registered_tickets()
,因为它似乎只能在事务中使用?我是否必须在这里违反DRY并将带有select_for_update()的查询复制并粘贴到代码中的其他位置?TransactionManagementError
。可能是什么原因,或者我误会了什么?答案 0 :(得分:1)
对select_for_update()
查询集执行EventRegistration
并不是路要走。这将锁定指定的行,但是可能您要防止的冲突涉及创建 new EventRegistrations
。您的锁不会阻止这种情况。
相反,您可以获得Event
上的锁。像这样:
class Event(models.Model):
...
@transaction.atomic
def reserve_tickets(self, number_tickets):
list(Event.objects.filter(id=self.id).select_for_update()) # force evaluation
if self.get_number_of_registered_tickets() + number_tickets <= self.capacity:
# create EventRegistration
else:
# handle error
请注意,这使用transaction.atomic
装饰器来确保您在事务内部运行。
答案 1 :(得分:0)
select_for_update
是使用Django实现的数据库功能选项。每当您编写操作进行一些更新时,数据库(在您的情况下为POSTGRES)都会照顾遵守这些ACID属性的可靠事务。
对我来说,您的方法似乎正确。最后一个问题的答案是使用time.sleep
延迟进行测试。
您可以执行选择操作,然后在下一行放入time.sleep(10)
,此时会在api上进行另一笔交易。您将能够找到
TransactionManagementError
答案 2 :(得分:0)
请注意,在多个数据库环境中,您必须在同一数据库上具有atomic和select_for_update,否则它将无法正常工作
with transaction.atomic(using='dbwrite'):
Model.objects.using('dbwrite').select_for_update()....