使用 get_or_create 返回的 MultipleObjects
Posted
技术标签:
【中文标题】使用 get_or_create 返回的 MultipleObjects【英文标题】:MultipleObjectsReturned with get_or_create 【发布时间】:2013-07-31 09:37:12 【问题描述】:我正在编写一个小的 django 命令来将数据从 json API 端点复制到 Django 数据库中。在我实际创建对象时,使用obj, created = model.objects.get_or_create(**filters)
,我收到MultipleObjectsReturned
错误。这让我很惊讶,因为我对get_or_create
的理解是,如果我尝试创建一个已经存在的对象,它只会“获取”它。
我不确定我正在克隆的数据库的完整性,但即使其中有多个相同的对象,当我将它们加载到我的本地 Django 数据库中时,get_or_create 也不应该这样做,这样我就永远不会获得多个副本?
谁能解释一下?我很乐意提供更多细节,我只是不想让读者陷入困境。
【问题讨论】:
get
搜索unique
元素如果有更多elements
会引发错误MultipleObjectsReturned
,过滤搜索大量elements
并返回其中的list
.
比较在这里***.com/questions/1541249/…
【参考方案1】:
示例代码
假设您有以下模型:
class DictionaryEntry(models.Model):
name = models.CharField(max_length=255, null=False, blank=False)
definition = models.TextField(null=True, blank=False)
以及以下代码:
obj, created = DictionaryEntry.objects.get_or_create(
name='apple', definition='some kind of fruit')
get_or_create
如果你还没有看到code for get_or_create
:
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True
return instance, created
关于网络服务器...
现在假设您有一个带有2
工作进程的网络服务器,它们都有自己的并发访问数据库。
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False # <===== nope not there...
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True
return instance, created
如果时机正确(或错误,取决于您要如何表达),两个进程都可以进行查找但找不到该项目。他们都可以创建项目。一切都很好……
MultipleObjectsReturned: get() returned more than one KeyValue -- it returned 2!
一切都很好......直到您第三次致电get_or_create
,他们说“第三次是一种魅力”。
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False # <==== kaboom, 2 objects.
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True
return instance, created
unique_together
你怎么能解决这个问题?也许在数据库级别强制执行约束:
class DictionaryEntry(models.Model):
name = models.CharField(max_length=255, null=False, blank=False)
definition = models.TextField(null=True, blank=False)
class Meta:
unique_together = (('name', 'definition'),)
回到函数:
# simplified
def get_or_create(cls, **kwargs):
try:
instance, created = cls.get(**kwargs), False
except cls.DoesNotExist:
instance, created = cls.create(**kwargs), True # <==== this handles IntegrityError
return instance, created
假设你和之前的比赛一样,他们都没有找到该项目并继续插入;这样做他们将开始交易,其中一个将赢得比赛,而另一个将看到IntegrityError
。
mysql ?
该示例使用TextField
,对于mysql
,它转换为LONGTEXT
(在我的例子中)。添加unique_together
约束会使syncdb
失败。
django.db.utils.InternalError: (1170, u"BLOB/TEXT column 'definition' used in key specification without a key length")
所以,运气不好,您可能不得不手动处理MultipleObjectsReturned
。
可能的解决方案
可以将TextField
替换为CharField
。
可以添加一个CharField
,它可能是TextField
的强哈希,您可以在pre_save
中计算并在unique_together
中使用。
【讨论】:
【参考方案2】:顾名思义,get_or_create
model.objects.get()
s 或model.objects.create()
s。
它在概念上等同于:
try:
model.objects.get(pk=1)
except model.DoesNotExist:
model.objects.create(pk=1)
来源是您找到这些类型问题的明确答案的地方。提示:搜索def get_or_create
。
可以看到,这个函数只在try/except中捕获DoesNotExist
。
def get_or_create(self, **kwargs):
"""
Looks up an object with the given kwargs, creating one if necessary.
Returns a tuple of (object, created), where created is a boolean
specifying whether an object was created.
"""
assert kwargs, \
'get_or_create() must be passed at least one keyword argument'
defaults = kwargs.pop('defaults', )
lookup = kwargs.copy()
for f in self.model._meta.fields:
if f.attname in lookup:
lookup[f.name] = lookup.pop(f.attname)
try:
self._for_write = True
return self.get(**lookup), False
except self.model.DoesNotExist:
【讨论】:
对,我的问题实际上更像是一种深思熟虑的类型。我看到get_or_create
只捕获DoesNotExist
。但是如果它确实存在,它不应该是self.get(**lookup)
吗?如果是这样,我的脚本怎么会创建第二个对象——因此我怎么能得到MultipleObjectsReturned
异常?
@bepetersn 你可能会得到一个副本,具体取决于你如何填充查询 kwargs(这实际上发生在我身上):get_or_create(name=foo), get_or_create(name=foo, bar=bar), get_or_create(name=foo) # multiple
啊,我明白了!所以我用create()
创建了两个不同的对象(b/c 的参数数量不同),然后当我尝试get()
只使用足够具体的参数来缩小对这两个对象的搜索范围时,我得到了一个错误。因此,从本质上讲,get(**params)
实际上只是过滤数据库。有点像select-where
SQL 语句。
再次感谢您一直支持我并帮助我解决这个问题,@Yuji
@Bepetersn,很好的发现 - 完全正确。使用更多参数获取或创建将导致单个参数重复。 NP人!【参考方案3】:
另一种可能导致 get_or_create() API 出现 MultipleObjectsReturned 错误的情况是,如果有多个线程同时使用同一组查询参数调用此 API。
仅依靠 try...catch... 在 Python 中创建唯一行是行不通的。如果您尝试使用此 API,我认为您应该对数据库中的相应列具有匹配的唯一性约束。
见:https://code.djangoproject.com/ticket/12579
【讨论】:
【参考方案4】:警告
假设数据库强制执行关键字参数的唯一性,此方法是原子的(请参阅 unique 或 unique_together)。如果关键字参数中使用的字段没有唯一性约束,则对该方法的并发调用可能会导致插入具有相同参数的多行。
https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.get_or_create
【讨论】:
以上是关于使用 get_or_create 返回的 MultipleObjects的主要内容,如果未能解决你的问题,请参考以下文章
与 iexact 一起使用时,Django get_or_create 无法设置字段
使用 User.objects.get_or_create() 在 django 中给出无效的密码格式?
django - 使用 get_or_create 自动创建用户时设置用户权限