python - 为什么 select_for_update 在并发插入中起作用?

标签 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())

这是我的模型:

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/

相关文章:

Django:CSRF验证失败

python - Pygame:设置视频模式而不显示显示

python - 在python中创建可以返回对象的lambda函数

PHP 脚本运行 SQL 更新命令,但它被忽略

sql - 在顶部 SQL 插入后减少列中的所有值

MySQL查询性能?

python - 如何在 Django 中发布请求后获取表单的名称?

Python:索引子字符串中的位置

python - D-Bus D-Feet发送字符串字典,Python语法中的变体

django - django 应用程序上的 pipelinenv 出现 Docker 错误 : Warning: --system is intended to be used for pre-existing Pipfile