2015-01-28 8 views
7

Korzystanie Django na bazie danych MySQL pojawia się następujący błąd:Unikanie MySQL impas w Django ORM

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

Usterka wzrasta w poniższym kodzie:

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

Mam kilka parallell procesy pracy na w tej samej bazie danych, a czasem mogą mieć to samo zadanie lub nakładać się w przykładowych danych. Dlatego muszę wyczyścić bazę danych, a następnie zapisać nowe próbki, ponieważ nie chcę żadnych duplikatów.

Uruchamiam całą sprawę w bloku transakcji with transaction.commit_on_success() i otrzymuję wyjątek OperationalError dość często. Wolałbym, aby transakcja nie zakończyła się impasem, ale zamiast tego po prostu blokuje i czeka na zakończenie drugiego procesu.

Z tego, co przeczytałem, powinienem zamówić zamki poprawnie, ale nie jestem pewien jak to zrobić w Django.

Jaki jest najprostszy sposób, aby upewnić się, że nie otrzymuję tego błędu, a jednocześnie upewnić się, że nie utracę żadnych danych?

Odpowiedz

5

Zastosowanie select_for_update() metoda:

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

pamiętać, że nie należy usuwać wybrane próbki i tworzyć nowe. Po prostu zaktualizuj przefiltrowane rekordy. Zablokowanie tych rekordów zostanie zwolnione, a transakcja zostanie zatwierdzona.

BTW zamiast __gte/__lte wyszukiwań można użyć __range:

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

Właśnie wypróbowałem 'select_for_update()' (i '__range'), ale wciąż widzę zakleszczenia. N.b. 'samples' nie pochodzi z bazy danych, ale pochodzi z rzeczywistego zadania przetwarzania. Baza danych jest właśnie używana do przechowywania niektórych informacji zebranych ze znacznie większego zbioru danych. – gurglet

+0

Zaktualizowałem odpowiedź. – catavaran

4

Aby uniknąć zakleszczenia, co zrobiłem było wdrożyć sposób ponawiania zapytanie w przypadku zakleszczenia się dzieje.

W tym celu zrobiłem małpę, łatając metodę "execute" klasy Django CursorWrapper. Ta metoda jest wywoływana, gdy kwerenda jest, więc to będzie działać w całej ORM i nie trzeba się martwić o zakleszczenia całym projekcie:

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 

Co Powyższy kod nie jest: to spróbuj uruchomić zapytanie i jeśli zgłoszony zostanie błąd OperationalError z kodem błędu 1213 (impasem), będzie czekał przez 200 ms i spróbuje ponownie. Wykona to 3 razy i jeśli po 3 razach problem nie zostanie rozwiązany, pierwotny wyjątek zostanie zgłoszony.

Ten kod powinien zostać wykonany, gdy projekt django jest ładowany do pamięci i dlatego dobrym miejscem na jego umieszczenie jest plik __ini__.py dowolnej aplikacji (umieszczam go w pliku głównym katalogu mojego projektu - __ini__.py - taki, który ma taką samą nazwę jak twój projekt django).

Mam nadzieję, że to pomoże każdemu w przyszłości.

+2

Obawiam się, że ten hak ** tylko powtarza ostatnie zapytanie DB **, które spowodowało błąd impasu. Ale baza danych ** w takim przypadku odwraca całą transakcję **.Tak więc, jeśli wykonywanie bardziej złożonej logiki składającej się z wielu zapytań DB w jednym bloku 'atomowym()', spowoduje to niepożądane zachowanie, ponieważ po tym wszystkim blok może się zakończyć bez zapisywania niektórych instrukcji w DB. –