Django:如何以线程安全的方式执行get_or_create()?

时间:2011-07-05 17:31:32

标签: python database django concurrency thread-safety

在我的Django应用程序中,我经常需要做类似于get_or_create()的操作。如,

  

用户提交标签。需要看看是否   该标记已存在于数据库中。   如果没有,请为其创建新记录。如果   它是,只是更新现有的   记录。

但是查看get_or_create()的文档看起来它不是线程安全的。线程A检查并查找记录X不存在。然后线程B检查并发现记录X不存在。现在,线程A和线程B都将创建一个新的记录X.

这一定是非常普遍的情况。我如何以线程安全的方式处理它?

4 个答案:

答案 0 :(得分:30)

自2013年左右以来,get_or_create是原子的,因此它可以很好地处理并发:

  

这个方法是原子假设正确使用,正确的数据库   配置和底层数据库的正确行为。   但是,如果未在数据库级别强制执行唯一性   在get_or_create调用中使用的kwargs(请参阅unique或unique_together),   这种方法容易出现竞争条件,可能导致多重竞争   同时插入具有相同参数的行。

     

如果您使用的是MySQL,请务必使用READ COMMITTED隔离   级别而不是REPEATABLE READ(默认值),否则你可能会看到   get_or_create将引发IntegrityError但对象的情况   不会出现在随后的get()调用中。

来自:https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

以下是您如何做到这一点的示例:

使用unique = True定义模型:

class MyModel(models.Model):
    slug = models.SlugField(max_length=255, unique=True)
    name = models.CharField(max_length=255)

MyModel.objects.get_or_create(slug=<user_slug_here>, defaults={"name": <user_name_here>})

...或使用unique_togheter:

class MyModel(models.Model):
    prefix = models.CharField(max_length=3)
    slug = models.SlugField(max_length=255)
    name = models.CharField(max_length=255)

    class Meta:
        unique_together = ("prefix", "slug")

MyModel.objects.get_or_create(prefix=<user_prefix_here>, slug=<user_slug_here>, defaults={"name": <user_name_here>})

请注意非唯一字段在默认值dict中的位置,而不是get_or_create中的唯一字段。这将确保您的创建是原子的。

以下是它在Django中的实现方式:https://github.com/django/django/blob/fd60e6c8878986a102f0125d9cdf61c717605cf1/django/db/models/query.py#L466 - 尝试创建一个对象,捕获一个最终的IntegrityError,并在这种情况下返回副本。换句话说:处理数据库中的原子性。

答案 1 :(得分:11)

  

这一定是非常普遍的情况。我如何以线程安全的方式处理它?<​​/ p>

SQL中的“标准”解决方案是简单地尝试创建记录。如果它有效,那很好。继续。

如果尝试创建记录从RDBMS获得“重复”异常,则执行SELECT并继续。

然而,Django有一个ORM层,它有自己的缓存。因此,逻辑被反转以使常见案例直接且快速地工作,并且不常见的案例(副本)引发罕见的异常。

答案 2 :(得分:3)

尝试使用event.commit_on_success装饰器来调用你正在尝试的地方get_or_create(** kwargs)

“使用commit_on_success装饰器为函数中的所有工作使用单个事务。如果函数成功返回,那么Django将在该点提交函数内完成的所有工作。如果函数引发异常, ,Django将回滚交易。“

除此之外,在对get_or_create的并发调用中,两个线程都试图获取带有参数的对象传递给它(除了“defaults”arg,这是在创建调用期间使用的dict,以防get()无法检索任何宾语)。如果失败,两个线程都会尝试创建对象,从而产生多个重复对象,除非在数据库级别使用get()调用中使用的字段实现一些唯一/唯一。

它类似于这篇文章 How do I deal with this race condition in django?

答案 3 :(得分:1)

这么多年过去了,但没有人写过from common.scriptFileName import GetJobDoneClass 。如果您没有机会为 threading.Lock 进行迁移,出于遗留原因,您可以使用锁或 unique together 对象。这是伪代码:

threading.Semaphore