Безопасен ли поток get_or_create()
У меня есть модель Django, доступ к которой возможен только с помощью get_or_create(session=session)
, где сеанс является внешним ключом для другой модели Django.
Поскольку я получаю доступ только через get_or_create()
, я бы предположил, что у меня будет только один экземпляр с ключом к сеансу. Тем не менее, я нашел несколько экземпляров с ключами для одного сеанса. Что происходит? Является ли это условием гонки или работает get_or_create()
атомарно?
Ответы
Ответ 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
много объектов, использующих один и тот же сеанс, что не является проблемой, поскольку вы не пытаетесь одновременно получить доступ к сеансу для изменения некоторого атрибута.