get_or_create()线程是否安全

时间:2011-06-20 19:14:31

标签: django multithreading django-models

我有一个只能使用get_or_create(session=session)访问的Django模型,其中session是另一个Django模型的外键。

由于我只通过get_or_create()访问,我想我只会有一个带有会话密钥的实例。但是,我找到了多个具有同一会话密钥的实例。怎么了?这是竞争条件,还是get_or_create()原子运作?

5 个答案:

答案 0 :(得分:39)

不,get_or_create 不是原子

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

例如,如果用户打开了两个页面(或者同时执行了几个ajax请求),这可能会导致所有get失败,并且所有create都会导致{{{ 1}}一个新行 - 具有相同的会话。

当数据库通过某些get_or_create / unique 抓住重复问题时,仅使用unique_together 非常重要strong>,这样即使多个线程可以达到save(),只有一个会成功,而其他线程会引发一个你可以捕获并处理的IntegrityError。

如果您将get_or_create与(数组)字段在数据库中不唯一,您将在数据库中创建重复项,这很少是您想要的。

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

有了警告,正确使用get_or_create是一个易于阅读,易于编写的结构,完美地补充了数据库完整性检查。

参考和引用:

答案 1 :(得分:12)

Actualy它不是线程安全的,您可以查看QuerySet对象的get_or_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

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

答案 2 :(得分:7)

答案 3 :(得分:2)

我遇到了一个调用get_or_create

的视图的问题

我正在使用Gunicorn和多名工人,所以为了测试它我将工人数量改为1,这使问题消失了。

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

http://www.caktusgroup.com/blog/2009/05/26/explicit-table-locking-with-postgresql-and-django/

编辑:我在一个装饰器中包装了锁定语句,只是为了处理不支持它的数据库引擎(在我的情况下单元测试时是SQLite):

try:
    cursor.execute('LOCK TABLE %s IN %s MODE' % (model._meta.db_table, lock))
except DatabaseError: 
    pass

答案 4 :(得分:-2)

我认为这不是竞争条件。当2个或更多线程或进程尝试访问同一资源以同时修改它时,会出现竞争条件。您正在描述使用相同会话get_or_create许多对象的情况,这不是问题,因为您没有尝试同时访问会话以修改某些属性..