2014-11-27 9 views
6

Ho bisogno di assicurarmi che un oggetto letto dal database e riscritto, non possa essere modificato nel frattempo da un'altra richiesta/processo.Django transaction.atomic() garantisce atomico READ + WRITE?

Does transaction.atomic() lo garantisce?

Le mie prove finora mi dicono no. Se non c'è niente di sbagliato in loro, quale sarebbe il modo giusto per ottenere LETTURE e SCRITTURE atomici?


Il mio esempio che ho provato.

Mettere il Test classe da qualche parte nel modello. atomic_test.py e atomic_test2.py devono essere salvati come comandi di gestione. Esegui python manage.py atomic_test prima, quindi python manage.py atomic_test2. Il secondo script non si blocca e le sue modifiche vengono perse.

models.py

class Test(models.Model): 
    value = models.IntegerField() 

atomic_test.py

from django.core.management.base import NoArgsCommand 
from django.db import transaction 
from time import sleep 
from core.models import Test 

class Command(NoArgsCommand): 
    option_list = NoArgsCommand.option_list 

    def handle(self, **options): 
     Test.objects.all().delete() 
     t = Test(value=50) 
     t.save() 

     print '1 started' 
     with transaction.atomic(): 
      t = Test.objects.all()[0] 
      sleep(10) 
      t.value = t.value + 10 
      t.save() 
     print '1 finished: %s' %Test.objects.all()[0].value 

atomic_test2.py

from django.core.management.base import NoArgsCommand 
from django.db import transaction 
from time import sleep 
from core.models import Test 

class Command(NoArgsCommand): 
    option_list = NoArgsCommand.option_list 

    def handle(self, **options): 
     print '2 started' 
     with transaction.atomic(): 
      t = Test.objects.all()[0] 
      t.value = t.value - 20 
      t.save() 
     print '2 finished: %s' %Test.objects.all()[0].value 

risposta

6

Django di 012.è una sottile astrazione sulle funzionalità di transazione del database. Quindi il suo comportamento dipende molto dal livello del database, che a sua volta dipende da quale database è e da quale livello di isolamento della transazione viene utilizzato. Quindi, per capire veramente come funziona, leggere e comprendere la documentazione della transazione per il tuo database.

Per quanto riguarda il vostro caso di test specifico, il comportamento desiderato può essere ottenuto utilizzando il metodo select_for_update() su un queryset Django (se il database lo supporta). Qualcosa di simile:

in atomic_test.py

with transaction.atomic(): 
    t = Test.objects.filter(id=1).select_for_update()[0] 
    sleep(10) 
    t.value = t.value + 10 
    t.save() 

in atomic_test2.py

with transaction.atomic(): 
    t = Test.objects.filter(id=1).select_for_update()[0] 
    t.value = t.value - 20 
    t.save() 

Il secondo dovrebbe bloccare fino a quando il primo finisce, e vedere il nuovo valore di 60.

Altre opzioni includono l'uso del transac SERIALIZABLE livello di isolamento della domanda o utilizzando un blocco di riga, sebbene Django non fornisca alcuna API specifica per eseguirli.

+0

Grazie, è andato con 'select_for_update'. Sembra buono finora. Il valore finale dovrebbe essere 40 (50 + 10-20). – kev

1

Sarebbe molto meglio per aggiornare atomicamente sul database utilizzando la funzione F:

from django.db.models import F 
Test.objects.filter(id=1).update(value=F("value") + 10) 

(Questo genera SQL qualcosa di simile "UPDATE test_test SET value = value + 10 WHERE id = 1")