2012-04-03 3 views
11

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

SomeModel.objects.filter(id=1).update(some_field=100) 

Вместо:

sm = SomeModel.objects.get(id=1) 
sm.some_field=100 
sm.save() 

Ваше приложение relativly поточно и операция SomeModel.objects.filter(id=1).update(some_field=100) не будет переписывать данные в других областях модели.

Мой вопрос .. Если есть какой-нибудь способ сделать

SomeModel.objects.filter(id=1).update(some_field=100) 

, но с созданием объекта, если он не существует?

ответ

5
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}) 
+0

Если другой процесс создает объект между двумя строками, вызов create() вызовет IntegrityError. Вы также не устанавливаете идентификатор в вызове create(). – GDorn

+0

Хорошо, вы должны заботиться о IntegrityError. Будет редактировать код. – Nik

+0

Имейте в виду, что то, что вы разместили выше, уже находится в dev-версии наборов запросов django: https://docs.djangoproject.com/en/dev/ref/models/querysets/#update-or-create –

0

Вы можете использовать встроенный get_or_create Django, но он работает только с самой моделью, а не с запросом.

Вы можете использовать это так:

me = SomeModel.objects.get_or_create(id=1) 
me.some_field = 100 
me.save() 

Если у вас есть несколько потоков, ваше приложение будет необходимо определить, какой экземпляр модели является правильным. Обычно то, что я делаю, это обновить модель из базы данных, внести изменения, а затем сохранить ее, чтобы у вас не было долгого времени в отключенном состоянии.

+0

Да, и я знаю, но это не потокобезопасно. Он будет генерировать запрос типа UPDATE m SET field_1 = old_value1, field_2 = old_value2, some_field = 100' вместо 'UPDATE m SET some_field = 100'. –

+0

Я понимаю, о чем вы говорите. Для этого нет безопасного потока. Вы должны получать последнюю информацию из базы данных, прежде чем сохранять ее, если используете несколько потоков. – Jordan

+0

Кстати, код, который я опубликовал выше, будет «потокобезопасным», поскольку до тех пор, пока вы делаете get_or_create каждый раз, прежде чем хотите сделать обновление, он будет обновляться из базы данных. – Jordan

0

В django невозможно выполнить такую ​​операцию upsert с обновлением. Но QuerySet обновление метод возвращает количество отфильтрованных полей, так что вы можете сделать:

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-кода, но обновление или вставка операции не будет атомарным. Если вы создадите блокировку в таблице, вы убедитесь, что два объекта не будут созданы в двух других потоках.

+0

Он по-прежнему будет генерировать запрос типа INSERT INTO VALUES '. Это означает, что если мы будем обновлять в одном потоке 'A.one_field', а во втором потоке мы обновим' A.second_field' - у нас появятся проблемы. Последний обновитель удалит все обновленные поля со старыми данными. ** Стоп-блокировка - это анти-решение здесь. ** Он будет периодически увеличивать исключения, но не будет решать корень проблемы. –

1

вы нужен метод select_for_update() django (и бэкэнд, поддерживающий блокировку на уровне строк, например 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().

+0

'' update_or_create'' только в Django> = 1.7 – chaim

0

Я думаю, что если у вас есть критические требования к работе с атомами.Лучше спроектировать его на уровне базы данных вместо уровня ORM Django.

Система Django ORM фокусируется на удобстве, а не на производительности и безопасности. Иногда вам приходится оптимизировать автоматически сгенерированный SQL.

«Транзакция» в большинстве продуктивных баз данных обеспечивает блокировку и откаты базы данных.

В системах гибридного типа (mashup (hybrid)), или скажем, что ваша система добавила некоторые компоненты третьей части, такие как ведение журнала, статистика. Приложение в разных рамках или даже язык может одновременно обращаться к базе данных, добавление потоковой безопасности в Django в этом случае недостаточно.

+0

И снова. Я просто не хочу, чтобы django обновлял все поля при сохранении модели. Все решения на уровне базы данных здесь не будут работать. Поскольку сам django принимает ** старые значения ** из экземпляра модели и обновляет модель с ними, даже если мы ** изменили только одно поле ** в коде. –

+0

Если вам не нужен конечный результат, я имею в виду значение поля. Вы можете использовать систему очереди задач (например, сельдерей), настроить одного выделенного рабочего на update_or_create запись, все операции с базой данных будут выполняться по последовательности. –

+0

Сельдерей здесь переполнен. :) 'update_or_create' в ветке django dev уже, поэтому вопрос не является актуальным. –

-3
SomeModel.objects.filter(id=1).update(set__some_field=100) 
+0

Пожалуйста, объясните свой код! Ваш ответ в настоящее время проголосовали за закрытие. –

Смежные вопросы