MultipleObjects使用get_or_create返回

时间:2013-07-31 02:26:39

标签: django python-2.7

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

我不确定我正在克隆的数据库的完整性,但即使其中有多个相同的对象,当我将它们加载到我的本地Django数据库时,也不应该get_or_create使它成为我从来没有获得多份副本?

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

3 个答案:

答案 0 :(得分:22)

示例代码

想象一下,您有以下型号:

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工作进程的Web服务器,它们都有自己的并发访问到数据库。

 # 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?

该示例使用TextFieldmysql转换为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
  • 可以添加CharFieldTextField可以是pre_save的强哈希值,您可以在unique_together中计算并在{{1}}中使用。< / LI>

答案 1 :(得分:3)

顾名思义,get_or_create model.objects.get()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:

答案 2 :(得分:3)

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

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

请参阅:https://code.djangoproject.com/ticket/12579