【发布时间】:2021-09-14 07:01:40
【问题描述】:
我正在从事一个类似于电子商务的项目,其中我的模型代表存储中有一定数量的产品,用户可以购买一定数量的产品,只要它不超过存储的数量.当服务器收到购买同一产品的多个请求时,我想避免发生竞争情况。
class Product(models.Model):
amount_in_storage = models.PositiveIntegerField() # for design reasons, this amount is unchangeable, it must be only evaluated once during initialization like constants in C++
@property
def amount_available_for_purchase(self):
return self.amount_in_storage - Purchase.objects.filter(product=self.id).aggregate(models.Sum('amount'))["sum__amount"]
class Purchase(models.Model):
product = models.ForeignKey(Product, ...)
amount = models.PositiveIntegerField()
payment_method = ...
假设这是负责创建购买的代码块。
@atomic_transaction
def func(product_id, amount_to_purchase):
product = Product.objects.get(...)
if product.amount_available_for_purchase > amount_to_purchase:
# do payment related stuff
Purchase.objects.create(product=product, amount=amount_to_purchase)
# do more stuff
我想限制对这段代码的并发访问,理想情况下我想在if条件下获得read锁访问,这样如果多个线程尝试查看可用数量是否大于购买金额,其中一个线程必须等到交易完成,然后才会评估读取请求,所以我想像这样利用 Django 的 select_for_update 和 version 字段:
class Product(models.Model):
amount_in_storage = models.PositiveIntegerField()
version = models.PositiveIntegerField() # we use this field just to write to it, no reading will take place
@property
def amount_available_for_purchase(self):
return self.amount_in_storage - Purchase.objects.filter(product=self.id).aggregate(models.Sum('amount'))["sum__amount"]
我使用这个字段来获得这样的锁:
@atomic_transaction
def func(product_id, amount_to_purchase):
product = Product.objects.select_for_update.get(...)
# acquiring a lock
product.version += 1
product.save()
if product.amount_available_for_purchase > amount_to_purchase:
# do payment related stuff
Purchase.objects.create(product=product, amount=amount_to_purchase)
# do more stuff
使用select_for_update,如果多个线程到达版本修改行,只有第一个会评估,其余的则必须等到整个事务完成,因此在 if 条件下获取该行的读锁。换句话说,一次只有 1 个线程可以访问此代码块。
我的问题是:
- 我的方法是否符合我的想法?
- 这是一种干净的方法吗?如果没有,如何在不使代码库复杂化并进行重大重构的情况下实现这一目标?
【问题讨论】:
标签: python django concurrency transactions locking