Django get_或_create()是线程安全的吗

Django get_或_create()是线程安全的吗,django,multithreading,django-models,Django,Multithreading,Django Models,我有一个Django模型,只能使用get\u或\u create(session=session)访问,其中session是另一个Django模型的外键 由于我只通过get\u或\u create()进行访问,我可以想象,我只会有一个实例具有会话密钥。但是,我发现多个实例具有同一会话的密钥。发生了什么事?这是一个争用条件,还是get\u或\u create()以原子方式运行?实际上它不是线程安全的,您可以查看QuerySet对象的get\u或\u create方法的代码,基本上它的作用如下: t

我有一个Django模型,只能使用
get\u或\u create(session=session)
访问,其中session是另一个Django模型的外键


由于我只通过
get\u或\u create()
进行访问,我可以想象,我只会有一个实例具有会话密钥。但是,我发现多个实例具有同一会话的密钥。发生了什么事?这是一个争用条件,还是
get\u或\u create()
以原子方式运行?

实际上它不是线程安全的,您可以查看QuerySet对象的get\u或\u create方法的代码,基本上它的作用如下:

try:
    return self.get(**lookup), False
except self.model.DoesNotExist:
    params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
    params.update(defaults)
    obj = self.model(**params)
    sid = transaction.savepoint(using=self.db)
    obj.save(force_insert=True, using=self.db)
    transaction.savepoint_commit(sid, using=self.db)
    return obj, True

因此,两个线程可能会发现数据库中不存在该实例,并在连续保存它们之前开始创建一个新实例。

线程是一个问题,但在MySQL的默认隔离级别中,如果使用严重,则会导致
get\u或\u create
中断:


不,获取或创建不是原子的

它首先询问数据库是否存在令人满意的行;数据库返回,python检查结果;如果它不存在,它就会创建它。在
get
create
之间,任何事情都可能发生-并且与
get
条件对应的行可以由其他代码创建

例如,如果用户同时打开两个页面(或执行多个ajax请求),这可能会导致所有
get
失败,并导致所有
用户在同一会话中创建新行

因此,当数据库通过一些
unique
/
unique\u一起捕获复制问题时,必须仅使用
get\u或
,这样即使多个线程可以到达save()点,也只有一个线程会成功,其他人会提出一个完整的错误,你可以抓住并处理

如果对数据库中不唯一的(一组)字段使用
get\u或\u create
,则会在数据库中创建重复项,这很少是您想要的

更一般地说:不要依赖您的应用程序来强制唯一性并避免数据库中的重复!这就是数据库工作! (除非你用一些操作系统的有效锁来包装你的关键函数,但我仍然建议你使用数据库)

有了这些警告,正确使用
get\u或\u create
是一个易于阅读、易于编写的结构,它完美地补充了数据库完整性检查

参考文献和引文:


我在调用
get\u或\u create
的视图时遇到了这个问题

我对多个工人使用Gunicorn,因此为了测试它,我将工人的数量更改为1,这使问题消失

我找到的最简单的解决方案是锁定表以进行访问。我使用此修饰符对每个视图进行锁定(对于PostgreSQL):

编辑:我用try/except将lock语句包装在该decorator中,以处理不支持它的DB引擎(在我的例子中是单元测试时的SQLite):


我不希望它是线程安全的…相关的:这些答案都过时了。请参阅以获得最新的解释。我不明白您如何得出它不是线程安全的结论;至少您的代码片段没有完全反映当前的实现(据我所知,甚至没有反映以前的实现)。的确,该实现曾经有过一个问题,但在@mvid发出请求之前就已经解决了。您省略了额外的try-and-get,因此,如果基础数据库对用于get部分的字段具有唯一索引,则只会插入一个线程,其他线程将获得;如果所有线程都使用相同的(或足够合理的)默认值,那么一切都会顺利进行。许多否定和合理的(关于先决条件的正确答案),但是,嘿,最后,这意味着get_或_create并不总是坏的,如果满足了所有标准,那么它就可以正常工作。。。你的回答遗漏了那一部分@ClassStacker的确如此——已经有了答案,我想补充一下。但你是对的,这绝对不坏,实际上它是一些有用的语法糖!我完全理解你补充其他职位的动机。然而,有趣的是,我正在寻找一个关于get_或create的讨论,得出一个结论。但我发现只有一半真实的声明和额外的博客链接,所以当我看到你的A时,我发现它简短而真实,但遗憾的是,对于那些正在寻找一点方向的人来说,没有一个结论。这就是评论。)@ClassStacker我理解你的观点——但有一个已经被接受的解决方案:)我编辑了我的,使它更全面。里程数可能因数据库而异,但不会太多。关于你的精确点,我需要知道你的“会话”密钥是如何生成的——以及你为什么不使用它!很酷的编辑我不是OP,所以不能告诉你OP的动机。不知道为什么会被否决。这对我起了作用。谢谢。锁定整个表是一个巨大的性能瓶颈。这是难以置信的沉重,你可能会关闭整个网站,而你正在服务一个单一的请求。它将适用于非常小的应用程序,并且不会扩展。
try:
    cursor.execute('LOCK TABLE %s IN %s MODE' % (model._meta.db_table, lock))
except DatabaseError: 
    pass