Подтвердить что ты не робот

Как я могу справиться с этим состоянием гонки в джанго?

Этот код должен получать или создавать объект и при необходимости обновлять его. Код используется на веб-сайте.

В некоторых случаях - когда база данных занята - она ​​выдает исключение "DoNotExist: запрос соответствия MyObj не существует".

# Model:
class MyObj(models.Model):
    thing = models.ForeignKey(Thing)
    owner = models.ForeignKey(User)
    state = models.BooleanField()
    class Meta:
        unique_together = (('thing', 'owner'),)

# Update or create myobj
@transaction.commit_on_success
def create_or_update_myobj(owner, thing, state)
    try:
        myobj, created = MyObj.objects.get_or_create(owner=user,thing=thing)

    except IntegrityError:
        myobj = MyObj.objects.get(owner=user,thing=thing)
        # Will sometimes throw "DoesNotExist: MyObj matching query does not exist"

    myobj.state = state
    myobj.save()

Я использую базу данных innodb mysql на ubuntu.

Как мне безопасно справиться с этой проблемой?

4b9b3361

Ответ 1

Это может быть отключение той же проблемы, что и здесь:

Почему этот цикл не отображает обновленный счет объекта каждые пять секунд?

В принципе get_or_create может выйти из строя - если вы посмотрите на его источник, вы увидите, что это: get, if-problem: save + some_trickery, if-still-problem: снова получить, если-еще-проблема: сдаться и поднять.

Это означает, что если есть два одновременных потока (или процессов), выполняющих create_or_update_myobj, оба пытающиеся get_or_create один и тот же объект, тогда:

  • первый поток пытается его получить, но он еще не существует,
  • поэтому поток пытается создать его, но перед созданием объекта...
  • ... второй поток пытается его получить - и это, очевидно, терпит неудачу
  • теперь из-за установленного по умолчанию AUTOCOMMIT = OFF для подключения базы данных MySQLdb и уровня сериализуемого REPEATABLE READ, оба потока заморозили свои представления таблицы MyObj.
  • впоследствии, первый поток создает свой объект и возвращает его изящно, но...
  • ... второй поток не может создать ничего, поскольку он нарушит ограничение unique
  • какой смешной, последующий get во втором потоке не видит объект, созданный в первом потоке, из-за замороженного представления таблицы MyObj

Итак, если вы хотите безопасно get_or_create что-нибудь, попробуйте что-то вроде этого:

 @transaction.commit_on_success
 def my_get_or_create(...):
     try:
         obj = MyObj.objects.create(...)
     except IntegrityError:
         transaction.commit()
         obj = MyObj.objects.get(...)
     return obj

Отредактировано 27/05/2010

Существует также второе решение проблемы - используя уровень изоляции READ COMMITED, а не REPEATABLE READ. Но он менее тестировался (по крайней мере, в MySQL), поэтому с ним может быть больше ошибок/проблем, но, по крайней мере, он позволяет привязывать представления к транзакциям без фиксации в середине.

Отредактировано 22/01/2012

Вот некоторые хорошие сообщения в блоге (не мои) о MySQL и Django, связанные с этим вопросом:

http://www.no-ack.org/2010/07/mysql-transactions-and-django.html

http://www.no-ack.org/2011/05/broken-transaction-management-in-mysql.html

Ответ 2

Ваша обработка исключений маскирует ошибку. Вы должны передать значение для state в get_or_create() или установить значение по умолчанию в модели и базе данных.

Ответ 3

Один (немой) способ мог бы поймать ошибку и просто повторить один или два раза после ожидания небольшого количества времени. Я не эксперт по БД, поэтому может быть сигнальное решение.