我有一个只能使用get_or_create(session=session)
访问的Django模型,其中session是另一个Django模型的外键。
由于我只通过get_or_create()
访问,我想我只会有一个带有会话密钥的实例。但是,我找到了多个具有同一会话密钥的实例。怎么了?这是竞争条件,还是get_or_create()
原子运作?
答案 0 :(得分:39)
不,get_or_create 不是原子。
它首先询问DB是否存在令人满意的行;数据库返回,python检查结果;如果它不存在,它会创建它。在get
和create
之间可能发生任何事情 - 并且其他代码会创建与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)
线程是一个问题,但get_or_create
因MySQL默认隔离级别的任何严重用法而被破坏:
答案 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
许多对象的情况,这不是问题,因为您没有尝试同时访问会话以修改某些属性..