2011-07-05 7 views
18

Nella mia app Django molto spesso ho bisogno di fare qualcosa di simile a get_or_create(). Ad es.,Django: come fare get_or_create() in modo thread-safe?

L'utente invia un tag. Hai bisogno di vedere se quel tag è già nel database. In caso contrario, creare un nuovo record per questo. Se lo è , aggiorna il record esistente .

Ma esaminando il documento per get_or_create() sembra che non sia protetto da thread. Il thread A controlla e trova che Record X non esiste. Quindi Thread B controlla e trova che Record X non esiste. Ora sia il Thread A che il Thread B creeranno un nuovo Record X.

Questa deve essere una situazione molto comune. Come posso gestirlo in modo sicuro?

+1

Uno dei due thread riceverà un errore di registrazione duplicato e un'eccezione. Non ci saranno dati duplicati. –

risposta

10

Questa deve essere una situazione molto comune. Come posso gestirlo in modo sicuro?

Sì.

La soluzione "standard" in SQL è semplicemente il tentativo di creare il record. Se funziona, va bene. Continua così.

Se un tentativo di creare un record ottiene un'eccezione "duplicata" da RDBMS, quindi eseguire un SELEZIONA e continuare.

Django, tuttavia, ha uno strato ORM, con la propria cache. Quindi la logica è invertita per far sì che il caso comune funzioni direttamente e rapidamente e il caso raro (il duplicato) solleva una rara eccezione.

+0

Ho riscontrato voci duplicate in un database postgres che avrebbe dovuto essere univoco quando stavo usando 'get_or_create' in un metodo di visualizzazione che stava ricevendo richieste simultanee, penso che questo sia un problema valido. –

+1

@A Lee: con vincoli di indice univoci correttamente definiti, un duplicato non dovrebbe essere possibile. In che modo sei riuscito a eludere il vincolo di indice univoco? –

+0

Ah, questo avrebbe risolto il problema ora che ci penso più chiaramente. 'Get_or_create' ha usato più campi e l'ho spostato su un percorso di esecuzione diverso invece di lasciarlo nella vista e aggiungere un vincolo univoco tra i campi del modello multiplo. –

3

prova transaction.commit_on_success decoratore per richiamabile in cui si sta tentando get_or_create (**) kwargs

"Usa il decoratore commit_on_success di utilizzare una singola transazione per tutto il lavoro fatto in un function.If la funzione restituisce con successo, quindi Django impegnerà tutto il lavoro svolto all'interno della funzione in quel punto, ma se la funzione solleva un'eccezione, Django eseguirà il rollback della transazione. "

a prescindere da esso, nelle chiamate simultanee a get_or_create, entrambi i thread cercano di ottenere l'oggetto con argomento passato ad esso (eccetto per l'argomento "default" che è un dict utilizzato durante la chiamata di creazione nel caso che get() non riesca a recuperare qualsiasi oggetto). in caso di errore entrambi i thread tentano di creare l'oggetto risultante in più oggetti duplicati a meno che alcuni unici/unici insieme siano implementati a livello di database con i campi usati nella chiamata di get().

è simile a questo post How do I deal with this race condition in django?

+1

Questo non è effettivamente necessario, vedere le mie altre risposte per i modi migliori per gestire questo. –

27

Dal 2013 o giù di lì, get_or_create è atomico, in modo che gestisce concorrenza bene:

Questo metodo è atomico assumendo un corretto utilizzo, corretta database di configurazione , e corretto comportamento del database sottostante. Tuttavia, se l'univocità non viene applicata a livello di database per i kwarg utilizzati in una chiamata get_or_create (vedere unique o unique_together), questo metodo è soggetto a una condizione di competizione che può comportare più righe con gli stessi parametri di inserito contemporaneamente.

Se si sta utilizzando MySQL, assicurarsi di utilizzare il READ COMMITTED livello di isolamento piuttosto che REPEATABLE READ (il default), altrimenti si rischia di vedere casi in cui get_or_create alzerà un IntegrityError ma l'oggetto non apparirà in una successiva chiamata get().

Da: https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

Ecco un esempio di come si potesse fare:

definire un modello unico nel suo genere sia con = True:

class MyModel(models.Model): 
    slug = models.SlugField(max_length=255, unique=True) 
    name = models.CharField(max_length=255) 

MyModel.objects.get_or_create(slug=<user_slug_here>, defaults={"name": <user_name_here>}) 

... o utilizzando unique_togheter :

class MyModel(models.Model): 
    prefix = models.CharField(max_length=3) 
    slug = models.SlugField(max_length=255) 
    name = models.CharField(max_length=255) 

    class Meta: 
     unique_together = ("prefix", "slug") 

MyModel.objects.get_or_create(prefix=<user_prefix_here>, slug=<user_slug_here>, defaults={"name": <user_name_here>}) 

Nota come i campi non univoci sono nei valori di default dict, NON tra i campi unici in get_or_create. Questo assicurerà che le tue creazioni siano atomiche.

Ecco come è implementato in Django: https://github.com/django/django/blob/fd60e6c8878986a102f0125d9cdf61c717605cf1/django/db/models/query.py#L466 - Provare a creare un oggetto, prendere un eventuale errore Integrity e restituire la copia in quel caso. In altre parole: gestisci l'atomicità nel database.

+2

Grazie a chi ha votato per questa risposta, ho aggiunto alcuni esempi per renderlo ancora più facile da capire. –

Problemi correlati