Django get_or_create 然后更新竞争条件

发布于 2025-01-15 09:57:16 字数 1146 浏览 3 评论 0原文

我有以下模型:

class UserProductBinding:
    product = models.ForeignKey(Product)
    user = models.ForeignKey(User)
    purchased = models.BooleanField(default=False)
    liked = models.BooleanField(default=False)

class Meta(object):
    unique_together = ['product', 'user']

现在我需要一个由用户购买产品的函数,该函数应该

  1. 获取具有相应用户和产品的 UserProductBinding,或者如果不存在则创建一个
  2. 确保“已购买”字段为 False
  3. 设置“购买”为 True 并从用户的余额中扣除金钱

我正在使用以下代码执行此操作:

binding, created = UserProductBinding.objects.get_or_create(product=product, user=user)
if binding.purchased:
   raise Error()
with transaction.atomic():
   binding.purchased = True
   user.money -= price
   binding.save()
   user.save()

问题是我在此代码中存在竞争条件。如果在短时间内在不同的线程中调用此函数两次,则“if binding.purchased:”检查有可能为两个线程返回 False,因此该函数将执行其工作两次,用户的钱将被扣除两次以及。

select_for_update 对我不起作用,因为如果绑定不存在,我需要创建绑定。 update_or_create 也不起作用,因为它在更新之前无法确保“购买”字段为假。

看起来使用 get_or_create 后跟 select_for_update 可以解决问题,但它需要额外的数据库命中。

我可以创建自己的 create_or_select_for_update 函数,这看起来并不复杂,但 Django 中缺少这样的函数这一事实让我觉得我可能走错了方向,并且可能遗漏了一些明显的东西。

提前致谢!

I have the following model:

class UserProductBinding:
    product = models.ForeignKey(Product)
    user = models.ForeignKey(User)
    purchased = models.BooleanField(default=False)
    liked = models.BooleanField(default=False)

class Meta(object):
    unique_together = ['product', 'user']

Now I need a function that makes a purchase of a product by a user, which should

  1. Get UserProductBinding with corresponsing user and product, or create one if it doesn't exist
  2. Make sure "purchased" field is False
  3. Set "purchased" to True and deduct money from user's balance

I'm doing it with the following code:

binding, created = UserProductBinding.objects.get_or_create(product=product, user=user)
if binding.purchased:
   raise Error()
with transaction.atomic():
   binding.purchased = True
   user.money -= price
   binding.save()
   user.save()

The problem is that I have a race condition in this code. If this function is called twice in separate threads within a short time period, there is a chance that "if binding.purchased:" check will return False for both threads, hence the function will do its job twice and user's money will be deducted twice as well.

select_for_update doesn't work for me, because I need to create the binding if it doesn't exist.
update_or_create doesn't work either because it doesn't make sure that "purchased" field is false before making an update.

Looks like using get_or_create followed by select_for_update would do the trick, but it requires additional database hit.

I could create my own create_or_select_for_update function, which doesn't seem very complicated, but the fact that such function is missing in Django makes me think that I'm maybe going the wrong direction and possibly missing something obvious.

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文