-
Notifications
You must be signed in to change notification settings - Fork 0
TECH TALK 7 BAZY DANYCH TRANSAKCJE BLOKADY
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
- 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
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”
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
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
- 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/