asn*_*zin 0 python sql django postgresql orm
我有一个代码,应该在并发请求和重负载下工作。
我写了一个例子来更好地理解我正在尝试做的事情:
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())
Run Code Online (Sandbox Code Playgroud)
这是我的模型:
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)
Run Code Online (Sandbox Code Playgroud)
我正在尝试并行插入 ManyToMany 关系表。显然,由于 READ COMMITED 隔离级别的原因,这会导致错误:
django.db.utils.IntegrityError: duplicate key value violates unique constraint
Run Code Online (Sandbox Code Playgroud)
绝对没问题,但是如何彻底消除这个错误呢?
为了保护我的图像,我尝试在图像选择上使用 select_for_update 。
image = Image.objects.select_for_update().get(pk=2)
Run Code Online (Sandbox Code Playgroud)
而且...它有效!我运行了好几次。不再有错误并且项目插入正确。但我不知道为什么?
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
Run Code Online (Sandbox Code Playgroud)
在数据库级别,您仅锁定Image要添加标签的特定实例。您是对的,这不会阻止插入到关系表中。如果另一段代码忽略锁并只是在关系表中插入新行,您仍然会遇到麻烦。
它适用于这段代码,因为每笔交易都是“行为良好”的。每个事务首先获取特定映像的锁,然后再将新条目添加到关系表中。这意味着执行程序池中的每个进程将等待当前进程完成其事务,然后再尝试在关系表中添加新行。
如果您锁定的是Tag而不是锁定,这也可以工作,但如果某些代码锁定,而其他代码锁定 ,则Image它不起作用。此时,一个进程可以获取 上的锁,但另一个进程不会等待,因为它仍然可以获取 上的锁,并且两个进程同时尝试将同一行插入到关系表中。TagImageImageTag
这就是我所说的“行为良好”的意思:应用程序的每个部分都必须以特定的方式运行(获取相同的锁)。如果应用程序的一部分忽略了这一要求,则可能会遇到竞争条件。只有当应用程序的所有部分都表现良好时,您才能以这种方式防止竞争条件。
| 归档时间: |
|
| 查看次数: |
1704 次 |
| 最近记录: |