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

Безопасен ли поток get_or_create()

У меня есть модель Django, доступ к которой возможен только с помощью get_or_create(session=session), где сеанс является внешним ключом для другой модели Django.

Поскольку я получаю доступ только через get_or_create(), я бы предположил, что у меня будет только один экземпляр с ключом к сеансу. Тем не менее, я нашел несколько экземпляров с ключами для одного сеанса. Что происходит? Является ли это условием гонки или работает get_or_create() атомарно?

4b9b3361

Ответ 1

Фактически это не потокобезопасно, вы можете посмотреть на код метода get_or_create объекта QuerySet, в основном, что он делает:

try:
    return self.get(**lookup), False
except self.model.DoesNotExist:
    params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
    params.update(defaults)
    obj = self.model(**params)
    sid = transaction.savepoint(using=self.db)
    obj.save(force_insert=True, using=self.db)
    transaction.savepoint_commit(sid, using=self.db)
    return obj, True

Таким образом, два потока могут понять, что экземпляр не существует в БД и начинает создавать новый, прежде чем сохранять их последовательно.

Ответ 2

НЕТ, get_or_create не атомный.

Сначала он запрашивает БД, если существует удовлетворяющая строка; возврат базы данных, результаты проверки python; если он не существует, он создает его. В промежутке между get и create может случиться что-то, и строка, соответствующая критериям get, создается другим кодом.

Например, по вашей конкретной проблеме, если две страницы открыты пользователем (или выполняется несколько запросов ajax), в то же время это может привести к сбою всех get, а для всех из них - к create a новая строка - с тем же сеансом.

Таким образом, важно использовать get_or_create, когда проблема дублирования будет обнаружена в базе данных через некоторый unique/unique_together, так что даже хотя несколько потоков могут перейти к точке save(), только один из них будет успешным, а остальные вызовут IntegrityError, с которым вы можете поймать и обработать.

Если вы используете get_or_create с (набором) полей, которые не уникальны в базе данных, вы создадите дубликаты в своей базе данных, что редко вам нужно.

В целом: не полагайтесь на свое приложение, чтобы обеспечить уникальность и избежать дубликатов в вашей базе данных! Это база данных! (хорошо, если вы не закроете свои критические функции некоторыми блокируемыми действиями ОС, но я все же предлагаю использовать базу данных).

С предупреждениями, которые использовались правильно get_or_create - это легко читаемая, легко записываемая конструкция, которая отлично дополняет проверки целостности базы данных.

Refs and citations:

Ответ 3

Threading - одна из проблем, но get_or_create нарушается для любого серьезного использования в уровне изоляции по умолчанию MySQL:

Ответ 4

У меня возникла эта проблема с представлением, которое вызывает get_or_create.

Я использовал Gunicorn с несколькими рабочими, поэтому, чтобы проверить его, я изменил число рабочих на 1, и это заставило проблему исчезнуть.

Самое простое решение, которое я нашел, это заблокировать таблицу для доступа. Я использовал этот декоратор для блокировки для каждого представления (для PostgreSQL):

http://www.caktusgroup.com/blog/2009/05/26/explicit-table-locking-with-postgresql-and-django/

EDIT: я завернул оператор блокировки в этом декораторе в try/except, чтобы иметь дело с механизмами DB без поддержки для него (SQLite при модульном тестировании в моем случае):

try:
    cursor.execute('LOCK TABLE %s IN %s MODE' % (model._meta.db_table, lock))
except DatabaseError: 
    pass

Ответ 5

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