我是Django的新手,我不知道如何管理并发。
我正在建立一个预备设备的应用程序。 我有一个带有ManytomanyField of Equipments的预订模型。 当我创建新预订时,我必须确保所有设备都可用。 我的代码的第一个版本不关心并发:
def validate_reservation(keys, begin, end):
eqpts = Equipment.objects.filter(pk__in=keys)
if all(eqpt.is_available(begin, end) for eqpt in eqpts):
res = Reservation(eqpts, begin, end)
res.save()
return True
else:
return False
is_available方法检查给定日期是否已有任何预留:
def is_available(self, begin, end):
return not self.reservations.filter(begin__lte=end, end__gte=begin).exists()
问题是运行此代码的两个用户可能会创建冲突预订,如果第一个人在第二次检查设备可用性后保存他的预订。
我相信我可以用交易来解决问题,所以我想出了这个:
def validate_reservation(keys, begin, end):
with transaction.atomic():
eqpts = Equipment.objects.select_for_update().filter(pk__in=keys)
if all(eqpt.is_available(begin, end) for eqpt in eqpts):
res = Reservation(eqpts, begin, end)
res.save()
return True
else:
return False
这是否有预期的行为? 如何知道交易是否失败并通知用户?
有不同的更好的方法吗?
答案 0 :(得分:3)
是的,只要您在修改可能影响可用性检查的任何内容之前始终使用select_for_update()
,这应该有效。
此代码不会干扰自身,因为select_for_update()
将在开头获取键行的行级锁定。但是,假设您有另一种观点,允许人们输入预约pk
并更改开始和结束时间,或添加新设备。这可能会导致不一致,除非您在与select_for_update()
相关联的Equipment
行上明确执行相同的Reservation
。
请注意,您的支票效率不高。无论何时你在循环中执行查询,你都应该寻找替代方案。在您的情况下,您应该能够找到与单个查询(JOINs
表)的冲突:
has_conflict = Reservation.objects.filter(begin__lte=end,
end__gte=begin,
equipment__pk__in=keys).exists()
(当然,您仍然存在并发问题,因此您仍需要获取适当的锁或使用不同的事务隔离级别。)
如何知道交易是否失败并通知用户?
事务不会失败,它只会阻塞,直到锁被释放。如果您希望事务失败,可以使用select_for_update(nowait=True)
(在某些数据库上)。