Django select_for_update 可以用来获取读锁吗?
Can Django select_for_update be used to acquire a read lock?
我正在做一个类似于电子商务的项目,在这个项目中,我有一些模型来表示存储中有一定数量的产品,用户可以购买一定数量的产品,只要它不超过存储量。我想避免在服务器收到多个购买同一产品的请求时发生竞争条件。
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
如果多个线程到达版本修改行,只有第一个会评估,其余的必须等到整个事务完成,因此获取第一个行的读锁如果条件。换句话说,一次只有 1 个线程可以访问此代码块。
我的问题是:
- 我的方法是否符合我的预期?
- 这是一个干净的方法吗?如果不是,如何在不使代码库复杂化和进行重大重构的情况下实现这一点?
是的,你的做法非常正确,它会在第一次查询时获取锁,其余的必须等到事务完成。
如果除了获取锁之外没有使用版本字段,您可以通过使用 list()
强制评估 QuerySet 来强制获取锁。请参阅下面的代码。
@atomic_transaction
def func(product_id, amount_to_purchase):
# Forcefully acquiring a lock
product = list(Product.objects.select_for_update.filter(...))[0]
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
在这里,您通过评估 docs 中提到的 QuerySet 来强制获取锁。
我正在做一个类似于电子商务的项目,在这个项目中,我有一些模型来表示存储中有一定数量的产品,用户可以购买一定数量的产品,只要它不超过存储量。我想避免在服务器收到多个购买同一产品的请求时发生竞争条件。
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
如果多个线程到达版本修改行,只有第一个会评估,其余的必须等到整个事务完成,因此获取第一个行的读锁如果条件。换句话说,一次只有 1 个线程可以访问此代码块。
我的问题是:
- 我的方法是否符合我的预期?
- 这是一个干净的方法吗?如果不是,如何在不使代码库复杂化和进行重大重构的情况下实现这一点?
是的,你的做法非常正确,它会在第一次查询时获取锁,其余的必须等到事务完成。
如果除了获取锁之外没有使用版本字段,您可以通过使用
list()
强制评估 QuerySet 来强制获取锁。请参阅下面的代码。@atomic_transaction def func(product_id, amount_to_purchase): # Forcefully acquiring a lock product = list(Product.objects.select_for_update.filter(...))[0] 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
在这里,您通过评估 docs 中提到的 QuerySet 来强制获取锁。