2012-04-03 10 views
11

Sappiamo che quell'aggiornamento è un'operazione thread-safe. Vuol dire, che quando si fa:Django. Thread sicuro aggiornamento o creare.

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

Invece di:

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

L'applicazione è thread relativly sicuro e il funzionamento SomeModel.objects.filter(id=1).update(some_field=100) non riscrivere i dati in altri campi del modello.

La mia domanda è .. Se c'è un modo per fare

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

ma con la creazione di oggetti se non esiste?

risposta

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 

Penso che il codice fornito nella domanda non sia molto dimostrativo: chi vuole impostare l'id per il modello? Assumiamo abbiamo bisogno di questo, e abbiamo operazioni simultanee:

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

Con update_or_create funzione, dipende da quale filo viene prima, oggetto viene creato e aggiornato con un'eccezione. Questo sarà thread-safe, ma ha evidentemente scarsa utilità: dipende dalle condizioni di gara del valore di SomeModek.objects.get(some__unique_field=1).some_field potrebbe essere 1 o 2.

Django fornisce oggetti F, in modo che possiamo aggiornare il nostro codice:

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

Se un altro processo crea l'oggetto tra le due linee, la chiamata create() genererà un'eccezione Integrity. Inoltre non stai impostando l'id nella chiamata create(). – GDorn

+0

Ok, hai ragione, dovrebbe preoccuparsi di IntegrityError. Modifica il codice. – Nik

+0

Ricorda che ciò che hai postato sopra è già nella versione dev dei queryset di django: https://docs.djangoproject.com/en/dev/ref/models/querysets/#update-or-create –

0

È possibile utilizzare il get_or_create di Django incorporato, ma che opera sul modello stesso, anziché su un queryset.

è possibile utilizzare che come questo:

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

Se si dispone di più thread, la vostra applicazione sarà necessario determinare quale istanza del modello è corretto. Di solito ciò che faccio è aggiornare il modello dal database, apportare modifiche e quindi salvarlo, in modo da non avere molto tempo in uno stato disconnesso.

+0

Sì, e lo faccio, ma non è sicuro. Genererà query come 'UPDATE m SET field_1 = old_value1, field_2 = old_value2, some_field = 100' invece di' UPDATE m SET some_field = 100'. –

+0

Vedo cosa stai dicendo. Non esiste un modo sicuro per farlo. Dovresti ottenere le ultime dal database prima di salvarlo se stai utilizzando più thread. – Jordan

+0

A proposito, il codice che ho postato sopra sarebbe "thread-safe" perché fintanto che si ottiene il get_or_create ogni volta prima di voler fare un aggiornamento, esso si aggiorna dal database. – Jordan

0

È impossibile in django eseguire un'operazione così rapida, con aggiornamento. Ma queryset aggiornamento Numero metodo di ritorno di campi filtrati in modo da poter fare:

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

questo esempio, se per Postgres, è possibile utilizzarlo senza codice SQL, ma aggiornare o inserire operazione non sarà atomica. Se crei un lock on table sarai sicuro che due oggetti non verranno creati in altri due thread.

+0

Genererà ancora query come 'INSERT INTO VALUES '. Significa che se aggiorneremo in un thread 'A.one_field' e nel secondo thread aggiorneremo' A.second_field' - avremo problemi. L'ultimo aggiornamento cancella tutti i campi aggiornati con i vecchi dati. ** Blocco tabella - anti-soluzione qui. ** Solleverà eccezioni periodicamente, ma non risolverà la radice dei problemi. –

1

È vuoi il metodo select_for_update() di django (e un backend che supporta il blocco a livello di riga, come PostgreSQL) in combinazione con la gestione delle transazioni manuale.

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

Si noti che se qualche altro processo elimina l'oggetto tra le due query, si otterrà un'eccezione SomeModel.DoesNotExist.

Django 1.7 e versioni successive ha anche il supporto per le operazioni atomiche e un metodo integrato update_or_create().

+0

'' update_or_create''è solo in Django> = 1.7 – chaim

0

Penso che se avete esigenze critiche sulle operazioni atomiche.Faresti meglio a progettarlo a livello di database anziché a livello di Django ORM.

Il sistema Django ORM si concentra sulla convenienza invece che sulle prestazioni e sulla sicurezza. È necessario ottimizzare l'SQL generato automaticamente a volte.

"Transazione" nella maggior parte dei database produttivi consente il blocco del database e il rollback.

In sistemi mashup (ibridi), oppure dire che il sistema ha aggiunto alcuni componenti di terze parti, come la registrazione, le statistiche. L'applicazione in un framework diverso o persino in una lingua può accedere al database contemporaneamente, aggiungendo thread safe in Django non è sufficiente in questo caso.

+0

E ancora. Non voglio che Django aggiorni tutti i campi sul modello di salvataggio. Tutte le soluzioni a livello di database non funzioneranno qui. Perché django stesso prende ** vecchi valori ** dall'istanza del modello e aggiorna il modello con essi, anche se ** abbiamo modificato solo un campo ** nel codice. –

+0

Se non ti interessa il risultato finale, intendo il valore del campo. È possibile utilizzare un sistema di coda di attività (come il sedano), impostare un worker dedicato per update_or_creare il record, tutte le operazioni sul database verranno eseguite in sequenza. –

+0

Il sedano è eccessivo. :) 'update_or_create' nel ramo dev django già, quindi la domanda non è attuale. –

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

Spiega il tuo codice! La tua risposta è attualmente votata per chiudere. –