Джанго. Потоковое обновление или создание.
Мы знаем, что обновление - это потокобезопасная работа.
Это означает, что когда вы делаете:
SomeModel.objects.filter(id=1).update(some_field=100)
Вместо:
sm = SomeModel.objects.get(id=1)
sm.some_field=100
sm.save()
Ваше приложение является релевантным потоком, и операция SomeModel.objects.filter(id=1).update(some_field=100)
не будет переписывать данные в других полях модели.
Мой вопрос: если есть способ сделать
SomeModel.objects.filter(id=1).update(some_field=100)
но с созданием объекта, если он не существует?
Ответы
Ответ 1
from django.db import IntegrityError
def update_or_create(model, filter_kwargs, update_kwargs)
if not model.objects.filter(**filter_kwargs).update(**update_kwargs):
kwargs = filter_kwargs.copy()
kwargs.update(update_kwargs)
try:
model.objects.create(**kwargs)
except IntegrityError:
if not model.objects.filter(**filter_kwargs).update(**update_kwargs):
raise # re-raise IntegrityError
Я думаю, что код, представленный в вопросе, не очень показателен: кто хочет установить id для модели?
Предположим, что нам это нужно, и мы имеем одновременные операции:
def thread1():
update_or_create(SomeModel, {'some_unique_field':1}, {'some_field': 1})
def thread2():
update_or_create(SomeModel, {'some_unique_field':1}, {'some_field': 2})
С функцией update_or_create
, зависит от того, какой поток будет первым, объект будет создан и обновлен без исключения. Это будет потокобезопасным, но, очевидно, мало пользы: зависит от значения состояния гонки SomeModek.objects.get(some__unique_field=1).some_field
может быть 1 или 2.
Django предоставляет объекты F, поэтому мы можем обновить наш код:
from django.db.models import F
def thread1():
update_or_create(SomeModel,
{'some_unique_field':1},
{'some_field': F('some_field') + 1})
def thread2():
update_or_create(SomeModel,
{'some_unique_field':1},
{'some_field': F('some_field') + 2})
Ответ 2
Вы хотите django select_for_update() метод (и бэкэнд, поддерживающий блокировку на уровне строк, например PostgreSQL ) в сочетании с ручным управлением транзакциями.
try:
with transaction.commit_on_success():
SomeModel.objects.create(pk=1, some_field=100)
except IntegrityError: #unique id already exists, so update instead
with transaction.commit_on_success():
object = SomeModel.objects.select_for_update().get(pk=1)
object.some_field=100
object.save()
Обратите внимание, что если какой-либо другой процесс удаляет объект между двумя запросами, вы получите исключение SomeModel.DoesNotExist.
Django 1.7 и выше также имеют поддержку атомной операции и встроенный метод update_or_create().
Ответ 3
Вы можете использовать Django встроенный get_or_create, но он работает только с самой моделью, а не с запросом.
Вы можете использовать это следующим образом:
me = SomeModel.objects.get_or_create(id=1)
me.some_field = 100
me.save()
Если у вас несколько потоков, ваше приложение должно будет определить, какой экземпляр модели правильный. Обычно то, что я делаю, это обновить модель из базы данных, внести изменения, а затем сохранить ее, чтобы вы не долгое время находились в отключенном состоянии.
Ответ 4
В django невозможно выполнить такую операцию upsert с обновлением. Но метод обновления запроса возвращает количество фильтрованных полей, чтобы вы могли:
from django.db import router, connections, transaction
class MySuperManager(models.Manager):
def _lock_table(self, lock='ACCESS EXCLUSIVE'):
cursor = connections[router.db_for_write(self.model)]
cursor.execute(
'LOCK TABLE %s IN %s MODE' % (self.model._meta.db_table, lock)
)
def create_or_update(self, id, **update_fields):
with transaction.commit_on_success():
self.lock_table()
if not self.get_query_set().filter(id=id).update(**update_fields):
self.model(id=id, **update_fields).save()
этот пример, если для postgres вы можете использовать его без кода sql, но операция обновления или вставки не будет атомарной. Если вы создадите блокировку в таблице, вы будете уверены, что два объекта не будут созданы в двух других потоках.
Ответ 5
Я думаю, если у вас есть критические требования к работе с атомами. Лучше спроектировать его на уровне базы данных вместо уровня ORM Django.
Система Django ORM фокусируется на удобстве, а не на производительности и безопасности. Вы иногда должны оптимизировать автоматически сгенерированный SQL.
"Транзакция" в большинстве продуктивных баз данных обеспечивает блокировку и откаты базы данных.
В mashup (гибридных) системах или скажите, что ваша система добавила некоторые компоненты третьей части, такие как ведение журнала, статистика. Приложение в разных рамках или даже язык может одновременно обращаться к базе данных, добавление потоковой безопасности в Django в этом случае недостаточно.
Ответ 6
SomeModel.objects.filter(id=1).update(set__some_field=100)