2015-05-19 74 views
4

我对Django相当陌生,而且我不确定如何管理并发。确保保存模型时的原子性

我正在构建预留设备的应用程序。 我有一个ManytomanyField的设备预订模型。 当我创建新的预订时,我必须确保所有设备都可用。 我的代码的第一个版本不关心并发:

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 

这是否有预期的行为? 如何知道交易是否失败并通知用户?

有没有不同的更好的方法?

回答

3

是的,只要您在修改任何可能影响您的可用性检查的内容之前一直使用select_for_update(),就应该可以工作。

此代码不会干扰自身,因为select_for_update()将在开始时获取关键行上的行级锁定。但假设您有另一种观点,允许人们输入预订pk并更改开始和结束时间,或添加新设备。这可能会导致不一致,除非您在与该Reservation关联的Equipment行上明确执行相同的select_for_update()

请注意,您的支票效率不高。每当你在一个循环中执行查询时,你应该寻找替代方案。你的情况,你应该能够找到一个单一的查询冲突(即JOINs表):

has_conflict = Reservation.objects.filter(begin__lte=end, 
              end__gte=begin, 
              equipment__pk__in=keys).exists() 

(当然,你仍然有并发问题,所以你仍然需要获得适当的锁或使用不同的事务隔离级别。)

如何知道事务是否失败并通知用户?

事务不会失败,它会一直阻塞直到锁定被释放。如果您希望交易失败,您可以使用select_for_update(nowait=True)(在某些数据库上)。

+0

感谢您的回答,并且更好的检查! 我的锁仍然适合您的支票,对不对?你能告诉我更多关于你提到的其他事务隔离级别吗? – Atn

+0

@An:你没有提到你的数据库,但[PostgreSQL文档](http://www.postgresql.org/docs/9.4/static/transaction-iso.html)提供了事务隔离级别的一个很好的描述。也就是说,我更喜欢你的排锁想法,并且它仍然可以和上面的检查一样。 –