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
вручную.
- https://code.djangoproject.com/ticket/2495
- https://code.djangoproject.com/ticket/12579
- http://django.readthedocs.org/en/latest/topics/db/transactions.html#using-a-high-isolation-level
- https://docs.djangoproject.com/en/dev/topics/db/transactions/#django.db.transaction.atomic
возможно решения
- можно заменить на
TextField
СCharField
. - его можно добавить
CharField
который может быть сильным хэшемTextField
, что вы можете вычислить вpre_save
и использовать вunique_together
.
другая ситуация, которая может вызвать ошибку MultipleObjectsReturned с GET_OR_CREATE () API, кажется, если есть несколько потоков, вызывающих этот API одновременно с тем же набором параметров запроса.
исключительно полагаясь на try... поймать... создать уникальную строку в Python не получится. Если вы пытаетесь использовать этот API, я думаю, у вас должно быть соответствующее ограничение уникальности для соответствующих столбцов в базе данных.
посмотреть: https://code.djangoproject.com/ticket/12579