Как мне справиться с этим состоянием гонки в Джанго? - PullRequest
28 голосов
/ 10 февраля 2010

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

В некоторых случаях - когда база данных занята - она ​​генерирует исключение «DoesNotExist: запрос на сопоставление 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.

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

Ответы [ 3 ]

41 голосов
/ 10 февраля 2010

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

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

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

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

  • Первый поток пытается получить его - но он еще не существует,
  • Итак, поток пытается создать его, но до создания объекта ...
  • ... второй поток пытается получить его - и это, очевидно, не удается
  • теперь, поскольку по умолчанию 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-транзакции, и-django.html

http://www.no -ack.org / 2011/05 / разбитая транзакция управление в-mysql.html

3 голосов
/ 10 февраля 2010

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

0 голосов
/ 10 февраля 2010

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

...