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

Con*_*ion 19 python database django concurrency thread-safety

在我的Django应用程序中,我经常需要做类似的事情get_or_create().例如,

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

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

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

Emi*_*röm 34

自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>})
Run Code Online (Sandbox Code Playgroud)

...或使用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>})
Run Code Online (Sandbox Code Playgroud)

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

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

  • 感谢投票给这个答案的人,我添加了一些例子,让它更容易理解. (2认同)

S.L*_*ott 11

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

是.

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

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

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

  • @A Lee:正确定义唯一索引约束后,应该不可能出现重复项。你是如何绕过唯一索引约束的? (2认同)