2012-04-03 19 views
11

Wiemy, że aktualizacja - jest bezpieczna dla wątków. Oznacza to, że gdy zrobisz:Django. Aktualizuj wątek lub utwórz.

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

Zamiast:

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

Aplikacja jest relativly wątek bezpieczny i operacja SomeModel.objects.filter(id=1).update(some_field=100) nie będzie przepisać dane w innych dziedzinach modelowych.

Moje pytanie brzmi .. Jeżeli istnieje jakikolwiek sposób zrobić

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

ale przy tworzeniu obiektu, jeśli nie istnieje?

Odpowiedz

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 

Myślę, że kod podany w pytaniu nie jest zbyt demonstracyjny: kto chce ustawić identyfikator dla modelu? Załóżmy musimy to i mamy jednoczesnych operacji:

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}) 

Z update_or_create funkcja zależy który wątek jest na pierwszym miejscu, obiekt zostanie utworzony i aktualizowany bez wyjątku. To będzie bezpieczny wątku, ale oczywiście ma niewielki wykorzystania: w zależności od rasy wartości warunku SomeModek.objects.get(some__unique_field=1).some_field może być 1 lub 2.

Django zapewnia obiektów F, dzięki czemu możemy zaktualizować nasz kod:

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

Jeśli inny proces utworzy obiekt między dwiema liniami, wywołanie create() spowoduje zgłoszenie wyjątku IntegrityError. Nie ustawiasz również identyfikatora w wywołaniu create(). – GDorn

+0

OK, masz rację, to powinno obchodzić IntegrityError. Dokona edycji kodu. – Nik

+0

Należy pamiętać, że to, co napisałeś powyżej, jest już w wersji dev zestawu zapytań django: https://docs.djangoproject.com/en/dev/ref/models/querysets/#update-or-create –

0

Możesz użyć wbudowanej funkcji Django get_or_create, ale działa ona na samym modelu, a nie na kwerendzie.

można wykorzystywać że tak:

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

Jeśli masz wiele wątków, aplikacja będzie musiała określić, które instancja modelu jest poprawna. Zwykle to, co robię, odświeżam model z bazy danych, wprowadzam zmiany, a następnie zapisuję go, abyś nie miał zbyt długiego czasu w stanie rozłączenia.

+0

Tak, i robię, ale to nie jest bezpieczne dla wątków. Wygeneruje zapytanie takie jak 'UPDATE m SET field_1 = old_value1, field_2 = old_value2, some_field = 100' zamiast' UPDATE m SET some_field = 100'. –

+0

Widzę, co mówisz. Nie ma sposobu, aby to zrobić w bezpieczny sposób. Powinieneś pobrać najnowsze z bazy danych, zanim ją zapiszesz, jeśli używasz wielu wątków. – Jordan

+0

Nawiasem mówiąc, kod, który napisałem powyżej, byłby "bezpieczny dla wątków", ponieważ tak długo, jak robisz get_or_create za każdym razem, zanim będziesz chciał dokonać aktualizacji, odświeżysz go z bazy danych. – Jordan

0

W django niemożliwe jest wykonanie takiej operacji z aktualizacją. Ale queryset aktualizacja numer metoda powrotu filtrowanych pól, dzięki czemu można zrobić:

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() 

Ten przykład jeśli dla PostgreSQL, można go używać bez kodu SQL, ale zmiana lub wstawić operacji nie będzie atomowa. Jeśli utworzysz blokadę na stole, będziesz mieć pewność, że dwa obiekty nie zostaną utworzone w dwóch innych wątkach.

+0

To nadal generuje zapytanie takie jak 'INSERT INTO VALUES '. Oznacza to, że jeśli będziemy aktualizować w jednym wątku 'A.one_field' iw drugim wątku będziemy aktualizować' A.second_field' - będziemy mieć kłopoty. Ostatni aktualizator usunie wszystkie zaktualizowane pola ze starymi danymi. ** Blokada stołu - tutaj nie ma rozwiązania. ** Będzie okresowo podnosić wyjątki, ale nie rozwiąże problemu. –

1

ty chce metody django select_for_update() (i backend, który obsługuje blokowanie na poziomie wiersza, takie jak PostgreSQL) w połączeniu z ręcznym zarządzaniem transakcjami.

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() 

Zauważ, że jeśli jakiś inny proces usuwa obiekt między dwoma zapytaniami, dostaniesz SomeModel.DoesNotExist wyjątek.

Django w wersji 1.7 i nowszych ma również obsługę operacji atomowych i wbudowaną metodę update_or_create().

+0

'' update_or_create''jest tylko w Django> = 1.7 – chaim

0

Myślę, że jeśli masz krytyczne wymagania dotyczące operacji atomowych.Lepiej zaprojektuj go na poziomie bazy danych zamiast poziomu Django ORM.

System ORM Django koncentruje się na wygodzie, a nie na wydajności i bezpieczeństwie. Musisz czasami zoptymalizować automatycznie generowany SQL.

"Transakcja" w najbardziej produktywnych bazach danych zapewnia dobrą blokadę i przywracanie bazy danych.

W systemach typu mashup (hybrydowych) lub powiedz, że system dodał niektóre komponenty trzeciej części, takie jak rejestrowanie, statystyki. Aplikacja w różnych ramach lub nawet język może uzyskać dostęp do bazy danych w tym samym czasie, dodając wątek bezpieczny w Django nie wystarczy w tym przypadku.

+0

I znowu. Po prostu nie chcę, aby django aktualizował wszystkie pola zapisu modelu. Wszystkie rozwiązania na poziomie bazy danych nie będą tutaj działać. Ponieważ django samo pobiera ** stare wartości ** z instancji modelu i aktualizuje z nimi model, nawet jeśli ** zmieniło tylko jedno pole ** w kodzie. –

+0

Jeśli nie dbasz o wynik końcowy, mam na myśli wartość pola. Możesz użyć systemu kolejki zadań (takiego jak seler), skonfigurować jednego dedykowanego pracownika, aby zaktualizować lub utworzyć rekord, wszystkie operacje w bazie danych będą wykonywane sekwencyjnie. –

+0

Seler jest tu przesadzony. :) 'update_or_create' już w gałęzi django dev, więc pytanie nie jest aktualne. –

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

Proszę wyjaśnij swój kod! Twoja odpowiedź brzmi: bieżący jest głosowany na zamknięcie. –