MultipleObjectsReturned с get или создать

Я пишу небольшую команду django для копирования данных из конечной точки JSON API в базу данных Django. В момент, когда я фактически создаю объекты, с obj, created = model.objects.get_or_create(**filters), Я получаю MultipleObjectsReturned ошибка. Это удивительно для меня, потому что мое понимание get_or_create это то, что если я попытаюсь создать объект, который уже существует, он просто "получит" его вместо этого.

Я не уверен в целостности базы данных, которую я клонирую, но даже если в ней есть несколько одинаковых объектов, когда я загрузите их в мою локальную базу данных Django, не должен ли get_or_create сделать так, чтобы я никогда не получал более одной копии?

кто-нибудь может это объяснить? Я рад дать более подробную информацию, я просто не хотел болтать читателя.

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. Как вы можете видеть, эта функция только ловит 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:

код

представьте, что у вас есть следующая модель:

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 ?

в примере используется 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.

другая ситуация, которая может вызвать ошибку MultipleObjectsReturned с GET_OR_CREATE () API, кажется, если есть несколько потоков, вызывающих этот API одновременно с тем же набором параметров запроса.

исключительно полагаясь на try... поймать... создать уникальную строку в Python не получится. Если вы пытаетесь использовать этот API, я думаю, у вас должно быть соответствующее ограничение уникальности для соответствующих столбцов в базе данных.

посмотреть: https://code.djangoproject.com/ticket/12579