2015-01-28 23 views
7

Utilizzando Django su un database MySQL ottengo il seguente errore:evitare stallo MySQL in Django ORM

OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction') 

La colpa sorge nel codice seguente:

start_time = 1422086855 
end_time = 1422088657 
self.model.objects.filter(
    user=self.user, 
    timestamp__gte=start_time, 
    timestamp__lte=end_time).delete() 

for sample in samples: 
    o = self.model(user=self.user) 
    o.timestamp = sample.timestamp 
    ... 
    o.save() 

Ho diversi processi parallell lavorare su lo stesso database e talvolta potrebbero avere lo stesso lavoro o una sovrapposizione nei dati di esempio. Ecco perché ho bisogno di cancellare il database e quindi memorizzare i nuovi campioni poiché non voglio duplicati.

Sto eseguendo il tutto in un blocco di transazione with transaction.commit_on_success() e sto ottenendo l'eccezione OperationalError abbastanza spesso. Quello che preferirei è che la transazione non finisce in un deadlock, ma invece blocca e aspetta che l'altro processo finisca con il suo lavoro.

Da quello che ho letto dovrei ordinare i blocchi correttamente, ma non sono sicuro di come farlo in Django.

Qual è il modo più semplice per garantire che non venga visualizzato questo errore assicurandomi comunque di non perdere alcun dato?

risposta

5

Uso select_for_update() metodo:

samples = self.model.objects.select_for_update().filter(
          user=self.user, 
          timestamp__gte=start_time, 
          timestamp__lte=end_time) 


for sample in samples: 
    # do something with a sample 
    sample.save() 

Notare che non è necessario eliminare i campioni selezionati e crearne di nuovi. Basta aggiornare i record filtrati. Il blocco per questi record verrà rilasciato, quindi la transazione verrà impegnata.

BTW invece di __gte/__lte le ricerche è possibile utilizzare __range:

samples = self.model.objects.select_for_update().filter(
          user=self.user, 
          timestamp__range=(start_time, end_time)) 
+1

Ho appena provato 'select_for_update()' (e '__range') ma sto ancora vedendo deadlock. N.B. 'samples' non proviene dal db, ma proviene dal processo di elaborazione vero e proprio. Il db è appena usato per memorizzare alcune delle informazioni raccolte da un set di dati molto più grande. – gurglet

+0

Ho aggiornato la risposta. – catavaran

4

Per evitare situazioni di stallo, quello che ho fatto è stato realizzare un modo di riprovare una query nel caso in cui una situazione di stallo che accade.

Per fare ciò, ho eseguito il patching del metodo "execute" della classe CursorWrapper di django. Questo metodo viene chiamato ogni volta che viene effettuata una query, in modo da funzionare su tutta l'ORM e non dovrete preoccuparvi di deadlock in tutto il progetto:

import django.db.backends.utils 
from django.db import OperationalError 
import time 

original = django.db.backends.utils.CursorWrapper.execute 

def execute_wrapper(*args, **kwargs): 
    attempts = 0 
    while attempts < 3: 
     try: 
      return original(*args, **kwargs) 
     except OperationalError as e: 
      code = e.args[0] 
      if attempts == 2 or code != 1213: 
       raise e 
      attempts += 1 
      time.sleep(0.2) 

django.db.backends.utils.CursorWrapper.execute = execute_wrapper 

Quello che il codice di cui sopra non è: si cercherà in esecuzione la query e se un OperationalError viene lanciato con il codice di errore 1213 (un deadlock), attenderà 200 ms e riproverà. Lo farà 3 volte e se dopo 3 volte il problema non è stato risolto, viene sollevata l'eccezione originale.

Questo codice deve essere eseguito quando il progetto django viene caricato in memoria e quindi un buon posto per metterlo è nel file __ini__.py di qualsiasi app (che ho inserito nel file __ini__.py della directory principale del mio progetto - il uno che ha lo stesso nome del tuo progetto Django).

Spero che questo aiuti chiunque in futuro.

+2

Ho paura che questo hook ** ripeta solo l'ultima query DB ** che ha provocato l'errore Deadlock. Ma Database ** ripristina l'intera transazione ** in tal caso.Pertanto, se si esegue una logica più complessa consistente in più query DB all'interno di un blocco 'atomic()', ciò comporterà un comportamento indesiderato perché dopo tutto il blocco potrebbe finire senza alcune delle affermazioni registrate nel DB. –