Джанго. Потоковое обновление или создание.

Мы знаем, что обновление - это потокобезопасная работа. Это означает, что когда вы делаете:

  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)