2009-10-29 16 views
20

Jak obsługiwać współbieżność w modelu Django? Nie chcę, aby zmiany w rekordzie były nadpisywane przez innego użytkownika, który czyta ten sam rekord.Kontrola współbieżności w modelu Django

+0

Możliwy duplikat http: // stackoverflow .com/questions/320096/django-how-can-i-protect-against-concurrent-modyfikacja-wpisów-bazy danych – Tony

Odpowiedz

22

Krótka odpowiedź, to naprawdę nie jest pytanie Django, jak przedstawiono.

Kontrola współbieżności jest często przedstawiana jako pytanie techniczne, ale pod wieloma względami jest kwestią wymagań funkcjonalnych. Jak chcesz/potrzebujesz, aby Twoja aplikacja działała? Dopóki się o tym nie dowiemy, trudno będzie udzielić jakiejkolwiek porady dotyczącej Django.

Ale czuję się jak wędrówki, tak tu idzie ...

Istnieją dwa pytania, na które staram się zadać sobie w konfrontacji z potrzebą kontroli współbieżności:

  • ile jest prawdopodobne, że dwóch użytkowników będzie musiało jednocześnie modyfikować ten sam rekord?
  • Jaki jest wpływ użytkownika na utratę jego modyfikacji w zapisie?

Jeśli prawdopodobieństwo zderzeń jest stosunkowo wysokie lub wpływ utraty modyfikacji jest poważny, być może patrzysz na jakąś pesymistyczną blokadę. W schemacie pesymistycznym każdy użytkownik musi uzyskać logiczną blokadę przed otwarciem rekordu do modyfikacji.

Pesykalne blokowanie ma wiele złożoności. Musisz zsynchronizować dostęp do zamków, rozważyć odporność na awarie, wygaśnięcia blokady, można zablokować blokady przez superużytkowników, czy użytkownicy zobaczą, kto ma blokadę, itd.

W Django można to zaimplementować za pomocą oddzielnego modelu blokady lub jakiegoś obcego klucza blokady użytkownika w zablokowanym rekordzie. Korzystanie ze stolika blokującego daje ci nieco więcej elastyczności w zakresie przechowywania po uzyskaniu blokady, użytkownika, notatek itp. Jeśli potrzebujesz uniwersalnego stolika blokującego, który może być użyty do zablokowania dowolnego rodzaju nagrania, spójrz na django.contrib.contenttypes framework, ale szybko może to przekształcić się w abstrakcyjny zespół astronautów.

Jeśli kolizje są mało prawdopodobne, a utracone modyfikacje są trywialnie odtwarzane, można funkcjonalnie uciec optymistycznym technikom współbieżności. Ta technika jest prosta i łatwiejsza do wdrożenia. Zasadniczo śledzisz numer wersji lub znacznik czasu modyfikacji i odrzucasz wszelkie modyfikacje, które wykrywasz jako bezsensowne.

Z punktu widzenia funkcjonalnego projektu, należy tylko zastanowić się, w jaki sposób te współbieżne błędy modyfikacji są prezentowane użytkownikom.

chodzi o Django, optymistyczne kontroli współbieżności mogą być realizowane poprzez nadpisanie metody zapisywania na swojej klasie modelu ...

def save(self, *args, **kwargs): 
    if self.version != self.read_current_version(): 
     raise ConcurrentModificationError('Ooops!!!!') 
    super(MyModel, self).save(*args, **kwargs) 

I, oczywiście, dla jednego z tych mechanizmów współbieżności być wytrzymałe, ty muszą rozważyć transactional control. Żaden z tych modeli nie działa w pełni, jeśli nie można zagwarantować właściwości ACID transakcji.

+0

Ok, ten ostatni przykład, który napisałeś, był tym, co chciałem wiedzieć, więc muszę napisać własną zapisz metodę dla modelu. Pochodzę z innej struktury, gdy chodzi o ustawienie właściwości "porównywania wartości" z formantem, więc nie miałem pojęcia, jak ją zaimplementować w Django, a nie znalazłem żadnego przykładu w początkowym samouczku, albo go przegapiłem. Pomyślałem, że skoro Django automatyzuje kilka zadań wykonywanych przez programistów w innych frameworkach, to zadanie może zostać zautomatyzowane, tak jak w ramce, o której mówiłem wcześniej: – Pablo

+0

Tak, moja osobista opinia jest taka, że ​​ramy powinny unikać generalizowania tych wzorców projektowych ze względu na wszystkie funkcjonalności./implikacje techniczne. YMMV ... –

+2

Jak wspomniano poniżej, kod w ostatnim fragmencie jest uszkodzony. Nadal może pojawić się modyfikacja między sprawdzaniem wersji a metodą zapisywania. – julkiewicz

10

Nie sądzę, że "zachowanie numeru wersji lub sygnatury czasowej" działa.

Gdy self.version == self.read_current_version() jest True, nadal istnieje szansa, że ​​numer wersji został zmodyfikowany przez inne sesje tuż przed wywołaniem super().save().

+2

Bez blokowania danej tabeli jest to poprawne. Istnieją jednak dekoratory upraszczające blokowanie tabel za pomocą modeli Django, które powinny unikać warunków wyścigu, do których się odnosisz. – Cerin

2

Zgadzam się z wprowadzającym wyjaśnieniem od Joe Holloway.

Chcę przyczynić się do sprawnego urywek w stosunku do ostatniej części jego odpowiedź („Jeśli chodzi o Django, optymistyczne kontroli współbieżności mogą być realizowane poprzez nadpisanie metody zapisywania na swojej klasie modelu ...”)

można wykorzystywać następujące klasy jako przodka dla własnego modelu

Jeśli jesteś wewnątrz transakcji db (np. za pomocą transaction.atomic w zakresie zewnętrznej) następujące oświadczenia python są bezpieczne i zgodne

W praktyce za pomocą jednego pojedynczego strzału, filtr instrukcji + aktualizacja zapewnia rodzaj testu d_set w rekordzie: weryfikują wersję i nabywają niejawnie blokadę poziomu db w wierszu. Zatem następujące "zapisz" jest w stanie zaktualizować pola rekordu, upewniając się, że jest to jedyna sesja działająca na tej instancji modelu. Ostateczna commit (na przykład wykonywany automatycznie przez __exit__ w transaction.atomic) zwalnia blokadę niejawny db na poziomie rzędu

class ConcurrentModel(models.Model): 
    _change = models.IntegerField(default=0) 

    class Meta: 
     abstract = True 

    def save(self, *args, **kwargs): 
     cls = self.__class__ 
     if self.pk: 
      rows = cls.objects.filter(
       pk=self.pk, _change=self._change).update(
       _change=self._change + 1) 
      if not rows: 
       raise ConcurrentModificationError(cls.__name__, self.pk) 
      self._change += 1 
     super(ConcurrentModel, self).save(*args, **kwargs) 

To jest pobierana z https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default