Skip to content

TECH TALK 7 BAZY DANYCH TRANSAKCJE BLOKADY

pawelkisielewicz edited this page Sep 21, 2018 · 14 revisions

Wprowadzenie

Autocommit – domyślne zachowanie django. Każde zapytanie jest automatycznie commitowane do bazy danych

Transakcje – lista operacji na bazie danych, które mogą zostać zatwierdzone (wykonane) jedynie wszystkie lub żadna

Typowy sposób rozwiązywania konfliktów w django to zamykanie każdego widoku w transakcje. W tym celu należy ustawić ATOMIC_REQUESTS na True. Sposób działania transakcji:

  • Widok/blok został wykonany bez błędów- django zatwierdza (commituje) transakcję i wszystkie zawarte

  • Widok/blok podczas wykonywania rzucił exception- django anuluje transakcję i *cofa wszystkie zmiany wprowadzane do bazy danych (rollback). Uwaga na łapanie wyjątków w transakcjach

Transakcję można rozpocząć na 2 sposoby:

Transakcja jako dekorator:

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

Transakcja jako context manager:

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

Domyślnie rozpoczynana jest transakcja dla bazy default. W przypadku większej liczby baz w projekcie należy określić, dla której z nich ma być rozpoczęta transakcja:

  • transaction.atomic**()** - rozpoczęcie transakcji dla bazy default
  • transaction.atomic**(using='my_base')** - rozpoczęcie transakcji dla bazy my_base

Savepoints

  • Savepoint to marker miejsca w kodzie oznaczający miejsce, do którego w przypadku błędów wykonywany jest rollback. Można dzięki temu określić punkt, „mówiąc – do tego miejsca wszystko jest ok”. Rollback wykonywany jest tylko do tego miejsca, zamiast całej transakcji
  • Są obsługiwane przez SQLite, PostgreSQL, Oracle, and MySQL
  • Używane tylko w porzypadku transakcji, w domyślnym trybie autocommit nie mają sensu
  • Użycie zagnieżdżonego bloku atomowego powoduje stworzenie savepointu na jego początku.
  • Wg dokumentacji django, lepiej korzystać z savepointów poprzez korzystanie z bloków atomowych niż tworzenie ich bezpośrednio w kodzie

Zagnieżdżanie bloków atomowych

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()
    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()
add_children()

W tle django wykonuje:

  • otwarcie transakcji przy wejściu do bloku atomowego
  • tworzy savepoint przy wejściu do wewnętrznego bloku
  • zwalnia savepoint lub rollback przy wyjściu z wewnętrznego bloku. Jeżeli poprawnie, całość wykonana „tak jakby nie było savepoint”
  • commit lub rollback całej, zewnętrznej transakcji

TransactionManagementError – rzucany przy próbie wywołań zapytań na bazie danych, gdy baza „uszkodzona” (przed rollback). W przykładzie handle_exception jest wywoływane już po rollback, baza jest „naprawiona”

Blokowanie bazy i wierszy

entries = Entry.objects.select_for_update().filter(author=request.user) Blokuje wszystkie zwracane rekordy do końca transakcji. Tylko zapytania od procesu, który założył blokadę są realizowane przez bazę danych.

select_for_update(nowait=False, skip_locked=False)

  • nowait = True – gdy wiersz zablokowany przez inną transakcję zgłasza błąd
  • nowait = False – gdy wiersz zablokowany przez inną transakcję czeka
  • skip_locked = True – pomija zablokowane wiersze

Select_for_update, delete (funkcje czekające na zwolnienie blokady) przy restarcie (po zwolnieniu blokady przez inny proces) wywołują jeszcze raz WHERE (SQL) i sprawdza, czy warunek filtrowania nadal jest spełniony. Jeżeli rekord został zmieniony i warunek WHERE nie jest już spełniony opełniony rekord nie jest pobierany i blokowany

on_commit()

from django.db import IntegrityError, transaction

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    with transaction.atomic():  # Inner atomic block, create a savepoint
        transaction.on_commit(bar)

# foo() and then bar() will be called when leaving the outermost block
with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    try:
        with transaction.atomic():  # Inner atomic block, create a savepoint
            transaction.on_commit(bar)
            raise SomeError()  # Raising an exception - abort the savepoint
    except SomeError:
        pass

# foo() will be called, but not bar()
  • Funkcje on_commit są wywoływane w kolejności, w jakiej były rejestrowane
  • Są wywoływane PO transakcji, nie są częścią transakcji. Błędy w funkcjach on_commit nie powodują rollback transakcji
  • TestCase izoluje każdy test jako osobną transakcję i wywołuje rollback po każdym teście (nawet pozytywnym, w celu zapewnienia izolacji). Żadna transakcja nie jest commitowana, przez co w testach dziedziczących po TestCase funkcje on_commit nigdy nie będą wywoływane. Do testowania resultatów on_commit należy użyć funkcji TransactionTestCase

on_commit vs rollback – generalnie lepiej stosować konstrukcję:

  • w przypadku sukcesu wykonaj – on_commit, niż:
  • w przypadku błedu wycofaj zmiany – rollback Przykładowo: wysłanie HTTP, task celery. Lepiej wysłać tylko w przypadku sukcesu (on_commit) niż w transakcji ryzykując rollback i wycofywanie zmian. Rollback prawdopodobnie byłby trudniejszy implementacyjnie, a czasami może być niemożliwy

Deadlocks - zakleszczenia

  • Zakleszczenia występują, gry dwie lub więcej transakcji blokują się nawzajem. Np. Transakcja A zakłada locka na wiersz ‘1’, w tym samym czasie transakcja B zakłada locka na wiersz ‘2’. Następnie transakcja A próbuje założyć locka na ‘2’, ale nie może i czeka na B, a B próbuje założyć lock na 1 i czeka na A.
  • PostgreSQL rozpoznaje takie sytuacje i rozwiązuje je anulując jedną z transakcji (trudno przewidzieć którą, można przyjąć że jest to losowo)

Część przykładów kopiowana z dokumentacji django https://docs.djangoproject.com/