【问题标题】:Ensure atomicity when saving a model保存模型时确保原子性
【发布时间】:2026-02-18 11:00:01
【问题描述】:

我对 Django 很陌生,我不知道如何管理并发。

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

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

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

有其他更好的方法吗?

【问题讨论】:

    标签: python django database concurrency atomic


    【解决方案1】:

    是的,只要您在修改任何可能影响可用性检查的内容之前始终使用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)(在某些数据库上)。

    【讨论】:

    • 感谢您的回答,以及更好的检查!我的锁仍然适合您的检查,对吗?你能告诉我更多关于你提到的其他事务隔离级别的信息吗?
    • @Atn:您没有提到您的数据库,但PostgreSQL documentation 很好地描述了事务隔离级别。也就是说,我更喜欢您的行锁想法,并且它仍然可以与上述检查一样工作。