我有一个代码,应该可以在并发请求和高负载下工作。
我写了一个例子来更好地理解我正在尝试做的事情:
def add_tag():
with transaction.atomic():
image = Image.objects.get(pk=2)
tag = Tag.objects.get(pk=6)
image.tags.add(tag) # concurrent insert
return 'done'
class Command(BaseCommand):
def handle(self, *args, **options):
with ProcessPoolExecutor(max_workers=3) as executor:
futures = []
for _ in range(3):
futures.append(executor.submit(add_tag))
for future in as_completed(futures):
print(future.result())
这是我的模型:
class Image(models.Model):
title = models.CharField(max_length=255)
tags = models.ManyToManyField('ov_tags.Tag')
class Tag(models.Model):
title = models.CharField(max_length=255)
我正在尝试并行插入 ManyToMany 关系表。显然,这会导致错误,因为 READ COMMITED 隔离级别:
django.db.utils.IntegrityError: duplicate key value violates unique constraint
绝对没问题,但是如何彻底消除这个错误?
为了保护我的图像,我尝试在图像选择上使用 select_for_update。
image = Image.objects.select_for_update().get(pk=2)
而且...它有效!我运行了几次。不再有错误,并且项目已正确插入。但我不知道为什么?
select_for_update 是否锁定关系表?还是发生在应用程序端?有实现这种行为的正确方法吗?
我可以使用空选择来锁定插入吗?
SELECT "image_tags"."tag_id" FROM "image_tags" WHERE ("image_tags"."tag_id" IN (6) AND "image_tags"."image_id" = 2) FOR UPDATE
最佳答案
在数据库级别,您只锁定特定的 Image
您要向其添加标签的实例。你是对的,这不会阻止插入到关系表中。如果另一段代码忽略了锁并简单地在关系表中插入一个新行,您仍然会遇到麻烦。
它适用于这段代码,因为每笔交易都是“行为良好”的。在向关系表添加新条目之前,每个事务首先获取特定图像上的锁。这意味着执行程序池中的每个进程将等待当前进程完成其事务,然后再尝试在关系表中添加新行。
如果您锁定 Tag
,这也会起作用而不是 Image
, 但如果某些代码锁定 Tag
则它不起作用, 而其他代码锁定 Image
.在这一点上,一个进程可以获得 Image
上的锁。 , 但另一个进程不会等待,因为它仍然可以获取 Tag
上的锁,并且两个进程都尝试同时将同一行插入到关系表中。
这就是我所说的“行为良好”的意思:应用程序的每个部分都必须以特定方式运行(获取相同的锁)。如果您的应用程序只有一部分忽略了这一要求,您可能会遇到竞争条件。只有当您的应用程序的所有部分表现良好时,您才能通过这种方式防止竞争条件。
关于python - 为什么 select_for_update 在并发插入中起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43514534/