21

我正在编写一个小的 django 命令来将数据从 json API 端点复制到 Django 数据库中。在我实际创建对象时obj, created = model.objects.get_or_create(**filters),我收到了一个MultipleObjectsReturned错误。这让我感到惊讶,因为我的理解get_or_create是,如果我尝试创建一个已经存在的对象,它只会“获取”它。

我不确定我正在克隆的数据库的完整性,但是即使其中有多个相同的对象,当我将它们加载到我的本地 Django 数据库中时,get_or_create 也不应该这样做,这样我就永远不会得到超过一份?

有人可以解释一下吗?我很乐意提供更多细节,我只是不想让读者陷入困境。

4

4 回答 4

27

示例代码

假设您有以下模型:

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

如果您还没有看到以下代码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?

该示例使用 a TextField,用于mysql转换为 a LONGTEXT(在我的情况下)。添加unique_together约束使syncdb.

django.db.utils.InternalError: (1170, u"BLOB/TEXT column 'definition' used in key specification without a key length")

所以,没有运气,你可能不得不MultipleObjectsReturned手动处理。

可能的解决方案

  • 可以将 替换TextFieldCharField
  • 可以添加 a CharField,它可能是 的强散列TextField,您可以pre_save在 a 中计算和使用unique_together
于 2015-04-08T17:12:03.657 回答
9

顾名思义,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。如您所见,此函数仅DoesNotExist在 try/except 中捕获。

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:
于 2013-07-31T04:14:38.697 回答
4

另一种可能导致 get_or_create() API 出现 MultipleObjectsReturned 错误的情况似乎是,如果有多个线程使用相同的查询参数集同时调用此 API。

仅依靠 try...catch... 在 Python 中创建唯一行是行不通的。如果您尝试使用此 API,我认为您应该对数据库中的相应列具有匹配的唯一性约束。

见:https ://code.djangoproject.com/ticket/12579

于 2014-02-06T11:00:31.963 回答
1

警告

此方法是原子的,假设数据库强制执行关键字参数的唯一性(请参阅 unique 或 unique_together)。如果关键字参数中使用的字段没有唯一性约束,则对该方法的并发调用可能会导致插入具有相同参数的多行。

https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.get_or_create

于 2021-12-09T22:30:28.713 回答