Współbieżność działania to cecha wszystkich współczesnych systemów komputerowych (pierwsze komputery pracowały sekwencyjnie, co oszczędzało wielu kłopotów, lecz niestety było dość wolne).
Przykład wsoółbieżnych działań: dwie osoby równocześnie pobierają 500 złotych z tego samego konta używając (różnych) bankomatów. System bazy danych powinien zadbać, żeby obie operacje zostały odnotowane, tzn. stan konta należy zmniejszyć dwukrotnie.
Podobna sytuacja występuje w innych programach. W wielu prostych edytorach tekstu jeśli dwie osoby równocześnie modyfikują ten sam dokument, utrwalone zostają tylko zmiany jednej z nich. Obecnie jest to jednak rzadko obserwowane, bo pracuje się głównie na komputerach osobistych — użytkownicy jawnie przesyłają sobie dokument.
Ale pamiętajmy, że istnieją rozproszone systemy plików (choćby NFS) i np. w laboratorium studenckim z serwerami plików może się zdarzyć, że dwie osoby będą modyfikować ten sam plik.
Transakcje to jedno z podstawowych pojęć współczesnych systemów baz danych. Umożliwiają one współbieżny dostęp do zawartości bazy danych, dostarczając niezbędnych mechanizmów synchronizacji.
Istotą transakcji jest integrowanie kilku operacji w jedną niepodzielną całość.
Popatrzmy na przykład wymagającu użycia transakcji. W systemie bankowym jest wykonywany przelew 100 złp z konta bankowego jednego klienta (np. Kangurzycy) na konto innego klienta (np. Tygrysa). W SQL wygląda to zapewne następująco
UPDATE Konta SET saldo = saldo - 100.00 WHERE klient = 'Kangurzyca'; UPDATE Konta SET saldo = saldo + 100.00 WHERE klient = 'Tygrys';
Co stanie się, jeśli po wykonaniu pierwszego polecenia nastąpi awaria dysku?
Podobny problem występuje nawet przy pojedynczym poleceniu SQL, jeśli modyfikuje ono wiele wierszy.
Rozwiązanie takich problemów to transakcyjny system baz danych. Gwarantuje on zapisanie modyfikacji w sposób trwały przed zakończeniem transakcji, a jeśli się nie uda to transakcja jest wycofywana.
Semantykę bazy danych określa się opisując zbiór legalnych stanów, czyli ograniczając dozwoloną zawartość tabel.
Operacje modeluje się jako funkcje:
Operacje powinny przeprowadzać legalne stany w legalne stany. Jednak w czasie wykonywania powiązanego ciągu operacji przejściowo baza danych może przyjmować nielegalne stany. Takie ciągi obudowujemy transakcjami i traktujemy transakcje jako niepodzielne operacje.
Transakcja to ciąg operacji do wspólnego niepodzielnego wykonania.
Współbieżne wykonywanie transakcji wymaga zachowania własności ACID (Atomicity, Consistency, Isolation, Durability):
niepodzielności: ,,wszystko-lub-nic”, transakcja nie może być wykonana częściowo;
integralności: po zatwierdzeniu transakcji muszą być spełnione wszystkie warunki poprawności nałożone na bazę danych;
izolacji: efekt równoległego wykonania dwu lub więcej transakcji musi być szeregowalny;
trwałości: po udanym zakończeniu transakcji jej efekty na stałe pozostają w bazie danych.
W trakcie wykonywania transakcja może być wycofana w dowolnym momencie. Wszelkie wprowadzone przez nią zmiany danych zostaną wtedy zignorowane.
Realizacja tego wymaga ,,tymczasowego” wykonywania transakcji. Zmiany danych są tylko obliczane i zapisywane w specjalnym dzienniku transakcji.
Po zakończeniu wykonywania transakcji następuje jej zatwierdzenie, w wyniku czego zmiany są utrwalane w bazie danych.
Odwiedźmy bazę danych piwiarni i zajmijmy się tabelą Sprzedaje(bar,piwo,cena).
Przypuśćmy, żę w barze ,,U Szwejka” sprzedaje się tylko dwa gatunki piwa: Okocim po 2,50 zł i Żywiec po 3,50 zł.
Dzielny redaktor gazety postanowił zbadać (używając naszej bazy danych), jaka jest najwyższa i najniższa cena piwa ,,U Szwejka”.
W tym samym czasie szef piwiarni zdecydował, że przestaje sprzedawać dotychczasowe piwa i przerzuci się na Heineken po 4,50 zł.
Pan redaktor wykonuje dwa następujące zapytania (po lewej stronie ich umowne nazwy)
(max) | SELECT MAX(cena) FROM Sprzedaje |
---|---|
WHERE bar = 'U Szwejka'; | |
(min) | SELECT MIN(cena) FROM Sprzedaje |
WHERE bar = 'U Szwejka'; |
A ,,równocześnie” szef piwiarni wykonał dwa inne polecenia SQL
(del) | DELETE FROM Sprzedaje |
---|---|
WHERE bar = 'U Szwejka'; | |
(ins) | INSERT INTO Sprzedaje |
VALUES('U Szwejka','Heineken',4.50); |
Przypuśćmy, że powyższe polecenia zostały wykonane w następującej kolejności: max, del, ins, min.
Popatrzmy na efekty:
Ceny | Operacja | Wynik |
{2.50,3.50} | max | 3,50 |
{2.50,3.50} | del | |
{} | ins | |
{4.50} | min | 4,50 |
A więc ostatecznie MAX(…) < MIN(…)!
Aby tego uniknąć, powinniśmy operacje poszczególnych osób pogrupować w transakcje.
Wtedy obie operacje pana redaktora wykonają się bezpośrednio po sobie, nie wiadomo tylko, czy przed, czy po zmianie ,,repertuaru”.
Szef piwiarni po wykonaniu (bez użycia transakcji) ciągu operacji (del)(ins) postanowił wycofać drugą z nich (ROLLBACK)
Jeśli redaktorowi udało się ,,wstrzelić” zapytanie między (ins) i ROLLBACK, zobaczy wartość (4,50), której nigdy nie było w bazie danych.
Rozwiązaniem jest znowu użycie transakcji:
Efekty transakcji nie są widziane przez innych, dopóki transakcja nie zostanie zatwierdzona (COMMIT).
Dla zapobiegania konfliktom używa się wewnętrznie blokowania dostępu do elementów danych używanych przez transakcję.
Poziomy ziarnistości blokad:
cała baza danych,
pojedyncza relacja,
blok wierszy,
pojedynczy wiersz.
Bezpośrednie (direct);
Konwersacyjne — kilkakrotna wymiana informacji klient/serwer;
Wiązane (chained) – wymagają przechowywania kontekstu;
Zagnieżdżone
Długotrwałe.
Kolejkowane – wykonywane z opóźnieniem, np. w celu grupowania;
Początek transakcji jest zwykle domyślny — pierwsza operacja na bazie danych wykonana przez aplikację.
W Postgresie przez można podać jawnie
BEGIN [WORK]
co jest najczęściej używane do wyjścia z trybu autocommit podczas pracy interakcyjnej.
Zakończenie transakcji następuje przez zatwierdzenie
COMMIT;
lub anulowanie (wycofanie)
ROLLBACK;
Uwaga: przy wystąpieniu błędu (np. naruszenie ograniczeń) ma miejsce niejawne wycofanie transakcji.
Domyślnie transakcje zezwalają na zapis. Rezygnuje się jednak z tego np. wtedy, gdy chcemy dokonać dłuższego skomplikowanego przeszukania spójnego stanu bazy danych.
Transakcję należy wtedy poprzedzić deklaracją
SET TRANSACTION LEVEL READ ONLY;
W transakcji takiej nie mogą wystąpić operacje modyfikacji, ale za to nie są widoczne zmiany dokonywane przez inne współbieżne transakcje.
Domyślnie przyjmowany jest poziom READ WRITE, tak jak gdyby podano
SET TRANSACTION LEVEL READ WRITE;
Poziom izolacji dla transakcji ustalamy korzystając z polecenia
SET TRANSACTION ISOLATION LEVEL [READ COMMITTED | SERIALIZABLE];
Poziom izolacji opisuje tylko, jak dana transakcja chce widzieć bazę danych (nie dotyczy więc bezpośrednio innych transakcji).
Poziom izolacji SERIALIZABLE gwarantuje semantykę sekwencyjną dla transakcji (ACID) przez wycofywanie transakcji naruszajacych ją.
Poziom READ COMMITTED powoduje przy modyfikacjach czekanie na zwolnienie (jawnej lub ukrytej) blokady wierszy. Odczyt nie jest jednak powtarzalny: kilka kolejnych wywołań tego samego zapytania w ramach tej samej transakcji może dać różne wyniki, ponieważ transakcja ma dostęp do wszystkich zatwierdzonych już modyfikacji z innych transakcji.
Standard dopuszcza również poziomy izolacji:
REPEATABLE READ, gdy odczyty w ramach transakcji dają zawsze te same wiersze co poprzednio, ale mogą się pojawić dodatkowe wiersze: ,,fantomy”. Żadne wiersze nie mogą jednak zniknąć.
READ UNCOMMITED, zezwalający na tzw. brudne odczyty (dirty reads): odczytanie danych zmodyfikowanych przez inną transakcję, która potem zostaje wycofana.
W tym przypadku domyślnym poziomem transakcji jest READ ONLY, ponieważ READ WRITE jest na ogół zbyt ryzykowny.
Blokady można zakładać na całą tabelę
LOCK TABLE tabela
IN [SHARE | EXCLUSIVE] MODE
[NOWAIT];
SHARE oznacza blokadę dzieloną (tylko przeciw zmianom).
EXCLUSIVE to wyłączna blokada dostępu (w celu dokonania zmian).
NOWAIT chroni przed czekaniem, gdy nie można natychmiast założyć blokady.
Zdjęcie blokad następuje przez wykonanie COMMIT lub ROLLBACK.
Lepiej jednak zakładać blokady na wybrane wiersze, np. gdy transakcja odczytuje pewne wiersze, a następnie dokonuje (zwykle w nich) zmian, można użyć
SELECT ... FOR UPDATE [NOWAIT];
Taka blokada też jest ważna do końca transakcji.
Dziennik transakcji do zapisywania wszystkich operacji.
Rejestrowanie wycofań w dzienniku.
Podczas odtwarzania powtarzamy tylko operacje z zatwierdzonych transakcji.
Inne podejście, dobre gdy głównie odczyty.
Optymistyczne, wycofanie transakcji gdy konflikt = fizycznie niemożliwy ciąg (lock wycofuje tylko gdy blokada).
Transakcja utożsamiana z momentem startu.
Treść automatycznie generowana z plików źródłowych LaTeXa za pomocą oprogramowania wykorzystującego LaTeXML.
strona główna | webmaster | o portalu | pomoc
© Wydział Matematyki, Informatyki i Mechaniki UW, 2009-2010. Niniejsze materiały są udostępnione bezpłatnie na licencji Creative Commons Uznanie autorstwa-Użycie niekomercyjne-Bez utworów zależnych 3.0 Polska.
Projekt współfinansowany przez Unię Europejską w ramach Europejskiego Funduszu Społecznego.
Projekt współfinansowany przez Ministerstwo Nauki i Szkolnictwa Wyższego i przez Uniwersytet Warszawski.