2015-01-28 6 views
7

Mit Django auf eine MySQL-Datenbank, die ich die folgende Fehlermeldung erhalten:Vermeidung von Deadlock MySQL in Django ORM

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

Der Fehler in dem folgenden Code steigt:

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

Ich habe mehrere parallell Prozesse arbeiten die gleiche Datenbank und manchmal haben sie möglicherweise den gleichen Job oder eine Überlappung in Beispieldaten. Deshalb muss ich die Datenbank löschen und dann die neuen Samples speichern, da ich keine Duplikate haben möchte.

Ich führe die ganze Sache in einem Transaktionsblock with transaction.commit_on_success() und bekomme oft die OperationalError Ausnahme. Was ich bevorzuge, ist, dass die Transaktion nicht in einem Deadlock endet, sondern nur sperrt und darauf wartet, dass der andere Prozess mit seiner Arbeit beendet wird.

Von dem, was ich gelesen habe, sollte ich die Schlösser richtig bestellen, aber ich bin mir nicht sicher, wie man das in Django macht.

Was ist der einfachste Weg um sicherzustellen, dass ich diesen Fehler nicht bekomme und trotzdem sicherstelle, dass ich keine Daten verliere?

Antwort

5

Verwenden select_for_update() Methode:

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

Beachten Sie, dass Proben ausgewählt nicht gelöscht werden sollte und neue schaffen. Aktualisieren Sie einfach die gefilterten Datensätze. Sperren für diese Datensätze werden freigegeben, dann wird Ihre Transaktion festgeschrieben.

BTW statt __gte/__lte Lookups können Sie __range verwenden:

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

Ich habe gerade 'select_for_update()' (und '__range') ausprobiert, aber ich sehe immer noch Deadlocks. N.b. 'samples' kommt nicht von der db, sondern von der eigentlichen Verarbeitung. Die Datenbank wird nur verwendet, um einige der Informationen zu speichern, die von einem viel größeren Datensatz gesammelt wurden. – gurglet

+0

Ich habe die Antwort aktualisiert. – catavaran

4

Deadlocks zu vermeiden, was ich tat, war eine Möglichkeit, erneut versucht eine Abfrage im Fall zu implementieren ein Deadlock passiert.

Um dies zu tun, was ich getan habe, war ich Affe gepatcht die Methode "Ausführen" von Django CursorWrapper-Klasse. Diese Methode wird aufgerufen, wenn eine Abfrage gemacht wird, so wird es über die gesamte ORM arbeiten und Sie werden über Ihr Projekt nicht über Deadlocks kümmern müssen:

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 

Was oben der Code tut, ist: es wird versuchen, läuft Wenn ein OperationalError mit dem Fehlercode 1213 (Deadlock) ausgelöst wird, wartet er auf 200 ms und versucht es erneut. Es wird dies dreimal tun und wenn nach 3 mal das Problem nicht gelöst wurde, wird die ursprüngliche Ausnahme ausgelöst.

Dieser Code ausgeführt werden sollte, wenn das django Projekt in den Speicher geladen wird und so einen guten Platz um es in der __ini__.py Datei aller Ihrer Apps ist (ich in der __ini__.py Datei meines Projektes Hauptverzeichnis abgelegt - die eine, die den gleichen Namen wie Ihr Django-Projekt hat).

Hoffe das hilft jedem in der Zukunft.

+2

Ich fürchte, dieser Haken ** wiederholt nur die letzte DB-Abfrage **, die zu einem Deadlock-Fehler geführt hat. Aber Datenbank ** setzt die gesamte Transaktion ** in einem solchen Fall zurück.Wenn also eine komplexere Logik bestehend aus mehreren DB-Abfragen innerhalb eines "atomic()" - Blocks ausgeführt wird, führt dies zu unerwünschtem Verhalten, da der Block schließlich enden kann, ohne dass einige der Anweisungen in der Datenbank aufgezeichnet werden. –