Transakcyj ność danych Michał Kwiatek W spółczesny programista oczekuje od bazy danych szeregu cech funkcjonalnych. ie cechy to, na przykład, możliwość definiowania widoków danych (views), kontrola dostępu albo wewnętrzny język programowania. Można wymienić wiele innych, ale bez wątpienia jedną z naj istotniejszych z nich jest obsługa transakcji. Co kryje się za tym pojęciem? Skojarzenie z operacjami finansowymi jest trafne - transakcje bankowe to sztandarowy przykład wykorzystania transakcji w bazach danych. Model ACO Rozważmy następujący scenariusz. W pierwszym kroku obsługi przelewu, od salda konta A ode jmowana jest kwota przelewu, która w drugim kroku ma zostać dodana do salda konta B. Do drugiego kroku jednak nie dochodzi, na przykład z powodu awarii. Konsekwencje byłyby straszne - w chwili przelewu giną pieniądze! Dlatego transakcje muszą być atomowe (ang. atomie), czyli niepodzielne. Jeżeli wszystkie elementy transakcji zostaną poprawnie przeprowadzone, transakcja jest zatwierdzana (ang. eommit), jeżeli choć jeden z jej elementów się nie uda, wówczas cała transakcja musi zostać odwołana (ang. ro/tbaek). Po zatwierdzeniu transakcji baza nie może zawierać błędnych danych, stąd mowa o spójności (ang. eonsisteney). Transakcje są również izolowane (ang. isolated) i trwałe (ang. durable). Pierwszy z tych terminów oznacza, że zmiany danych dokonane w obrębie transakcji są widoczne na zewnątrz dopiero po jej zatwierdzeniu. Drugi termin oznacza pewność, że wynik zatwierdzonej transakcji jest na dobre zapisywany w systemie i nie zginie nawet w przypadku awarii. Q W kolumnie k1 rekordu o id=n jest liczba 3 PoZOm izolacj: leac commltted ProceS 1 Proces 2 select k.1 trom t sełect k.1 trom t w/lere d:n where d:n zwraca 3 zwraca 3 update t set ki~ki+.1 where d=n; selct ki trem l t where idc" selct k1.from t where d=n zwraca 4 zwraca 3 commit; update t set 1<1=k.1+4 wbele id=n; Proces 2 Jest zablokowany do chwili gdy proces 1 wykona commit lub rollback commlł: select k1 trem t select ki from t where d=' where d=' zwraca 8 zwraca 8 RYSUNEK (1) - dea działania transakcji dla poziomu izolacji read committed. Te cztery zapewniane przez każdy system Autor jest programistą i projektantem w zespole portalu www.biznespartner.pl. Kontakt z autorem pod adresem: michal.kwiatek@biznespartner.pl transakcyjny cechy, w języku angielskim tworzą akronim aeid. Poziomy izolacji Standard języka SQL definiuje 4 poziomy izolacji transakcji (ang. isolation levels) - patrz Tabela CD. Kolejne poziomy oznaczają ochronę 18 www.software.com.pl Software 2 O 12/2001
Transakcyjność w bazach danych wymagające zastosowania TABELA (l) Możliwość zaistnienia niepożądanych zjawisk na poszczególnych poziomach wyższych poziomów izolacji izolacji. są zupełnie naturalne i wcale nie należą do Poziom izolacji Odczyt niezatwierdzonych rekordów Odczyt niepowtarzalny Odczyt widm rzadkości. frasobliwe Read uncommitted Możliwy Możliwy Możliwy ich potraktowanie zaowocuje usterkami, których Read committed możliwy Możliwy Możliwy przyczyny będą niezwykle Repeatable read możliwy możliwy MOŻliwy trudne do ustalenia. Co Serializable możliwy możliwy możliwy gorsza, usterki te ujawnią się dopiero przy dużym przed trzema niepożądanymi zjawiskami: odczytem nieza dopiertwierdzonych rekordów (ang. dirty read), odczytem niepo mieć zagrożenia wynikające ze stosowania niskiego poziomu w środowisku produkcyjnym. Dlatego warto rozuwtarzalnym (ang. nonrepeatable read) oraz odczytem widm izolacji i zawczasu się przed nimi zabezpieczyć. (ang. phantom read). Odczyt niezatwierdzonych rekordów, to sytuacja, w której w obrębie jednej transakcji widać niezatwierdzone zmiany na przykład, przeprowadzane w innej transakcji. Jeżeli, użytkownik zapyta o stan konta w chwili dokonywania przelewu, a przelew z pewnych przyczyn zostanie odwołany, to może się okazać, że użytkownik zobaczył saldo konta pomniejszone o kwotę przelewu. Jeżeli następnie użytkownik oprze na tej informacji inne obliczenia, to będą one bez sensu, gdyż przelew nigdy nie doszedł do skutku. Ochrona przed tym zjawiskiem jest stosunkowo prosta i opiera się na wykorzystaniu mechanizmu blokad (ang. locks). Prześledźmy sytuację na Rysunku (1). Jeżeli proces 1 zmienia rekord n, to na ten rekord zakładana jest blokada. nny użytkownik (proces 2) widzi zmieniony rekord w postaci sprzed rozpoczęcia transakcji procesu 1. Gdy proces 2 spróbuje zmienić ten sam rekord, wówczas zostanie zablokowany do momentu zatwierdzenia (lub odwołania) zmiany tego rekordu, czyli do chwili zakończenia transakcji procesu 1. Proszę również zauważyć, że zmiana dokonana w procesie 1 jest uwzględniana w obliczeniach procesu 2. Przed odczytem niezatwierdzonych rekordów chroni standardowy poziom izolacji: read committed. Odczyt niepowtarzalny polega na tym, że wyniki wykonania tego samego zapytania w jednej transakcji, ale w pewnym odstępie czasowym różnią się: rekordy z wyniku pierwszego wykonania zapytania nie występują w wyniku powtórnego wykonania, lub wartości rekordów zmieniły się. Aby uniknąć tego zjawiska, należy korzystać z poziomu izolacji repeatable read. Z odczytem widm mamy do czynienia, gdy w wyniku drugiego wykonania tego samego zapytania pojawiły się rekordy, których poprzednio nie było - rekordy widma. Zjawisko to nie występuje, o ile aktualnym poziomem izolacji jest serializable. W praktyce stosuje się głównie zapewniający podstawową update labie U Proces 1 jest setk1;k1~ poprawność poziom read committed. Co prawda, w stan blokowandardzie języka SQL za domyślny uznawany jest serializable, gdy proces2 wykona do momentu, where d=n; commit lub rollback ale żaden producent baz danych nie traktuje tego poważnie. Każdy z kolejnych poziomów izolacji zwiększa obciążenie systemu i ryzyko wystąpienia zakleszczeń (ang. deadlocks). zmniejsza możliwość równoległego dostępu do danych i, co za tym idzie, wydajność systemu. Zazwyczaj poziom read committed rzeczywiście wystarcza, jednakże sytuacje Zakleszczenia w naj prostszym, przedstawionym na Rysunku" 2) scenariuszu dochodzi do zakleszczenia, gdyż jeden proces jest zablokowany w oczekiwaniu na zasób zajmowany przez drugi proces, i vice versa. Proces 1 modyfikuje rekord w tabeli tl i tym samym blokowana jest możliwość zapisu w tym rekordzie dla procesu 2. Analogicznie blokowany jest zapis w Tabeli @ dla procesu l, gdyż proces 2 wykonał update. W kolejnym kroku proces 1 próbuje pisać w tym rekordzie i oczywiście jest blokowany. Ostatnim, prowadzącym do zakleszczenia krokiem jest próba modyfikacji rekordu w Tabeli (l) przez proces 2. Teraz proces 2 czeka na zasób, który może zwolnić proces 1, a proces 1 czeka na zasób zajmowany przez proces 2. Kółko się zamyka. Różne systemy z różną skutecznością radzą sobie z zakleszczeniami. System Oracle, postawiony przed sytuacją z Rysunku...2), przerywa proces 1 z błędem: ORA-00060: deadlock detected while waiting for resource. Z pewnością nie jest to jednak sytuacja pożądana - należy tak programować, żeby wykluczyć możliwość zakleszczeń. POZom lzoll:jl: read commłtlecl RYSUNEK l]) Zakleszczenie transakcji. obciążeniu systemu, często Proces 2 update table t2 set k2ak2+3 where id=m; Proces 2 jest blokowany do momentu gdy.proces 1 wykona commit lub rollback. Dochodzi do zakleszczenia. www.software.com.pl
TABELA CD Poziomy izolacji dostępne w popularnych bazach danych Poziom izolacji Oracle 91 Microsoft SQL Server 2000 P08tgreSQL 7.1 MySQL 3.23 Read uncommitted Read commltted Repeatable read serializable Uwagi Dodatkowy poziom Read only --------- Tylko dla tabel typu BerkleyDB lub nnodb Prostą, lecz skuteczną radą jest stosowanie jak najkrótszych zatwierdzenie transakcji. Z kolei, aby odwołać transakcję, transakcji, o możliwie niskim poziomie izolacji. Do poszcze należgólnych tabel należy się też odwoływać w tej samej kolejności, użyć polecenia roll back. Można też odwołać część o ile tylko jest to możliwe. Warto zauważyć, że do opisanego wyżej zakleszczenia nie doszłoby, gdyby procesy odwoływały się do Tabel 0 i 0w tej samej kolejności. Gdy zapytanie SQL, które powinno zadziałać natychmiast, a nie odpowiada przez dłuższy czas, wskazuje, że inna transakcja zajęła pewien pożądany zasób i jeszcze się nie skończyła. Zazwyczaj przyczyną takiej usterki jest po prostu niepotrzebne przeciąganie transakcji, która wykonała już wszystkie kluczowe operacje i z powodzeniem mogła zostać zatwierdzona. Warto o tym pamiętać, bo zapomniana transakcja zablokuje możliwość modyfikacji danych wszystkim innym użytkownikom. W praktyce... Transakcje są częścią standardu SQL, dlatego w większości popularnych systemów zarządzania relacyjną bazą danych ich obsługa jest dość podobna. W praktyce okazuje się, że poszczególne systemy mają różne możliwości (patrz Tabela 0). Dlatego wiedza o tym, co i w jaki sposób w konkretnej bazie danych można osiągnąć, jest niezwykle użyteczna. Poniżej opisuję cztery systemy, naj popularniejsze w zastosowaniach internetowych. Dwa z nich, MySQL i PostgreSQL, są darmowe, dostępne jako Open Source. Produkt Microsoftu posiada szerokie grono zwolenników, a Oracle oferuje bazy danych o uznanej marce. Wybór tych systemów, choć arbitralny, jest przekrojowy. Ponadto, informacje o innych produktach łatwiej zdobyć wiedząc, co oferuje konkurencja. transakcji przy pomocy rollback to, o ile wcześniej użyto instrukcji savepoi nt. Na Listingu CD przedstawiony jest mechanizm sel ect for update, szczególnie wygodny, gdy dane w rekordzie musimy zmodyfikować w sposób wykluczający użycie pojedynczej operacji update. W tej sytuacji należy uniemożliwić modyfikację tego rekordu z innej transakcji. Jeżeli tego nie zrobimy, ktoś inny może zmodyfikować wartość, którą my następnie zamażemy wynikiem obliczeń, uzyskanym bez uwzględnienia tej zmiany. W systemie bankowym doprowadziłoby to do wycieków gotówki. nną ciekawą cechą Oracle 'a jest możliwość definiowania transakcji autonomicznych, czyli takich, których działanie jest zatwierdzane (lub odwoływane) niezależnie od transakcji, z której zostały one wywołane. Nadaje się to szczególnie do logowania błędów w tabelach bazy danych. Przykład spełniającej to zadanie funkcji przestawiono na Listingu 2. Aby zaznaczyć, że dany blok programu ma stanowić transakcję autonomiczną, należy użyć dyrektywy (pragma) autonomous_transaction. Microsoft SQL Server 2000 W bazie danych Microsoftu poziom izolacji dla wszystkich transakcji w sesji ustala się poleceniem set t ransact i on i so l at i on l eve l. Wszystkie cztery poziomy izolacji są obsługiwane. Transakcję rozpoczynamy używając komendy begi n transaction, a zatwierdzamy i odwołujemy odpowiednio instrukcjami commit i rollback. Gracie Si aracie obsługuje poziomy izolacji read commited, seria izable, oraz dodatkowo transakcje tylko do odczytu, widzące jedynie te zmiany, które były zatwierdzone w chwili rozpoczęcia transakcji. W obrębie tego typu transakcji nie można dodawać, usuwać ani modyfikować wierszy. Do zmiany poziomu izolacji służą polecenia: set transaction isolation level (należy je wykonać jako pierwsze polecenie w transakcji) lub alter session set isolation level, które modyfikuje wszystkie późniejsze transakcje w obrębie danej sesji, czyli połączenia z bazą danych. Transakcje zatwierdza się poleceniem commi t, które jednocześnie rozpoczyna następną transakcję. Warto pamiętać, że każde polecenie języka definicji danych (DOL), np. create table, alter table etc. powoduje implicite LSTNG 1 Prezentacja mechanizmu select for upejste. Declare CURSOR c [S SELECT " FROM konto FOR UPDATE OF saldo; Saldo_teMP: KONTO.SALDO%TYPE: Begi n Set transaction isolation leve] read committed; FOr rekord: KONTO%ROWTYPE n C LOOP Sa ldo_temp: -ob l uga_kon t. dodalodsetki (record. onto; /"inne operacje na saldo_temp"/ UPDATe konto SEt saldo - saldo_temp WHERE CO E Of C; End loop: Commit ; End; 20 www.software.com.pl Software 2 O 12/2001
Transakcyjność w bazach danych LSTNG 2 Definicja transakcji autonomicznej. PROCEDURE loguj (wi adomosc VARCHAR) pragma autonomous_transacti on; Beg; n N5ERT NTO logi(tresc) VALUE5 (wiadomosc); Comm; t; End; W tym systemie istnieje możliwość zagnieżdżania transakcji. Otwarcie nowej transakcji w obrębie transakcji już działającej powoduje zwiększenie licznika @@trancount. Zysk jest pozorny, gdyż realnie i tak nie można odwołać ani zatwierdzić wewnętrznej transakcji. Jedynym efektem instrukcji commi t, wywołanej w transakcji wewnętrznej, jest zmniejszenię @@trancount. Dopiero gdy wartość @@trancount osiągnie wartość O, wszystkie zmiany zostaną zatwierdzone. Jeżeli zależy nam na odwołaniu jedynie części transakcji, należy skorzystać z mechanizmu savepointów (patrz część o OracJe 'u). Warto wiedzieć, że połączeni'e z SQL Serverem może działać w trybie niejawnych transakcji (ang. impjicit transactions). W tym przypadku polecenia takie jak se l ect, create etc. powodują otwarcie nowej transakcji, którą użytkownik musi potem zamknąć już Transakcja nie zostanie zatwierdzona do momentu, gdy sami tego nie zrolbimy. Domyślnie dla połączeń zupełnie jawnie. korzystających z OLE DB (w tym również Query AnaJyzera) niejawne transakcje są wyłączone, ale czasami możemy tę opcję nieświadomie włączyć, na przykład wykonując instrukcję set ans i_defaults on, Oprócz zwykłych kursorów for update, opisanych w części o OracJe'u, w SQL Serverze dostępne są optymistyczne kursory (ang. optimistic curso!'). Przy ich wykorzystaniu możliwe są pozycyjne modyfikacje rekordów poprzez kursor, analogicznie do sytuacji pokazanej na Listingu [), ale żadne rekordy nie są blokowane. Powoduje to wzrost wydajności. Bezpośrednio przed modyfikacją rekordu system sprawdza, czy ten rekord nie został przypadkiem wcześniej zmieniony przez kogoś innego. Jeżeli tak się stało, zgłaszany jest błąd, który może zostać przechwycony w aplikacji i odpowiednio tam zinterpretowany. Z optymistycznych kursorów warto korzystać, gdy prawdopodobieństwo zmiany danych jest niewielkie, a zależy nam na wydajności. PostrgreSGL 7.1 PostgreSQL obsługuje poziomy read committed i seriajibje. Transakcję rozpoczyna się poleceniem begi n, zatwierdza używając commi t, a odwołuje przy pomocy poleceń ro 11 ba ck i abort, które są synonimami. Do zmiany poziomu izolacji pojedynczej transakcji służy instrukcja set transaction isolat;on level, a dla wszystkich transakcji w sesji stosuje się komendę set session characteristics. Do obsługi równoczesnego dostępu do danych wykorzystywany jest model wielu wersji (mujti-version concurrency controd, co odróżnia PostgreSQLa od większości systemów opierających się na mechanizmie blokad. W PostgreSQLu blokady wykorzystywane są w mniejszym stopniu. O modelu wielu wersji najlepiej myśleć w następujący sposób: każda transakcja widzi dane w stanie, który był aktualny w pewnym momencie, ale od tamtej pory mógł zostać zmieniony. Daje to pewność, że nawet w przypadku modyfikacji widzimy spójne dane. Dodatkowo procesy modyfikujące dane nigdy nie blokują procesów czytających, ani na odwrót. Zwiększa to poziom równoległości systemu. Kosztem takiego rozwiązania jest dopisywanie nowych węzłów do indeksów przy każdej zmianie wartości. ndeksy są czyszczone dopiero w momencie wykonania operacji VACUUM. W rezultacie tablice, na których wykonuje się dużo zmian działają coraz wolniej. W wersji 7.1 do obsługi operacji zmieniających dane wykorzystywany jest dodatkowo specjalny dziennik WAL (Write-Ahead Lam. Każda zmiana rekordu zapisywana jest najpierw w dzienniku, a dopiero w chwili mniejszego obciążenia systemu (lub gdy użytkownik poprosi o to poleceniem checkpoi nt) zmiany są zapisywane w poszczególnych plikach danych. Dzięki temu nowa wersja PostgreSQLajest wydajniejsza i dużo bardziej odporna na awarie. MySGL 3.23 Wśród programistów aplikacji internetowych panuje pfzeświadczenie,że MySQLjest szybszy od PostgreSQLa, ale za to dużo uboższy w zakresie oferowanej funkcjonalności. Jedną z najczęściej wymienianych wad MySQLajest brak transakcji. Jest to prawda, ale nie do końca, gdyż począwszy od wersji 3.23 w MYSQL można wkompilować obsługę dwóch nowych typów tabel, które umożliwiają transakcyjny dostęp do rekordów. Należy jasno zauważyć, że te wspomniane tabele (chodzi o BerkJeyOB oraz JnnoOB) nie są zawarte w standardowych dystrybucjach binarnych. Oczywiste jest też, że korzystanie z transakcyjnych tabel pociągnie za sobą zauważalny spadek wydajności bazy. MySQL obsługuje wszystkie cztery poziomy izolacji. Do ich zmiany służy polecenie set transaction isolation l evel, które wydaje się przed rozpoczęciem nowej transakcji. Do rozpoczęcia transakcji służy begi n, ale trzeba pamiętać, aby uprzednio poleceniem set autocommi t - O zmienić domyślny tryb automatycznego zatwierdzania zmian. Transakcje zatwierdza się i odwołuje odpowiednio poleceniami commi t i roll back. Fakt współistnienia różnych typów tabel może łatwo doprowadzić do wystąpienia błędów. Dwa najczęściej spotykane scenariusze to próba obsługi transakcji w odniesieniu do tabel, które tego nie umożliwiają, oraz próba tworzenia tabel obsługujących transakcje bez uprzedniego wkompilowania ich obsługi w jądro MySQLa. W pierwszym przypadku MySQL automatycznie zatwierdzi wszystkie zmiany, w drugim bez ostrzeżenia utworzy tabele domyślnego typu MyjSAM, które nie obsługują transakcji. Autorzy MySQLa tłumaczą, że ma to służyć łatwej migracji danych pomiędzy systemami obsługującymi różne typy tabel, ale na pewno należy zachować ostrożność. dokończenie artykułu na stronie 26... www software com pl
W linii 8 zawarte jest odczytanie właściwości o nazwie name, zrzutowanie tej wartości na typ 5tri ng i wypisanie jej do klienta (w systemie LeftHand przyjęliśmy, że nie używamy domyślnych konwersji typów i wszystkie konwersje trzeba wykonywać explicite). 8. <%- it.get()->get("name")->to5tring().c_str() %>(BR> Dla znającego C++ programisty, kod taki jest z pewnoś bardzo czytelny. Manipulowanie poszczególnymi cią wartościami w węzłach odbywa się za pośrednictwem kilku prostych metod: get, put, remove. Oczywiście klasa może mieć dowolną ilość swoich własnych metod, jak również z łatwością może przeciążyć i zmienić działanie metod podstawowych. Dla porównania, na Ustingu :2 przedstawiono podobny kod, napisany w JavaScript. Jak widać, w JavaScript nie trzeba rzutować typów Uest to język z typami dynamicznymi). Widać też, że konstruktor dla L_root bez parametrów wskazuje na węzeł aktualnej aplikacji. wiem jak Wam, ale nam się bardziej podoba C++. LSTNG 2 Wypisanie wszystkich klas w aplikacji w JavaScript. <% var oroot- new L_root(); var oclasses - oroot.get("classes"); var it - new L_iterator_props(oclasses); while (!it.end(» ( %> <%- it.get().get("name")%><br> <% it.next() ; ) %> Podsumowanie Połączenie serwera aplikacyjnego z drzewiastą bazą danych daje bardzo dużo korzyści. Po pierwsze, kod powstaje w jednym języku i jest pisany w jednej metodologii - obiektowej. Systemy powstają w bardzo uporządkowany sposób - żeby nie powiedzieć, że podręcznikowy. Możemy w sposób obiektowy planować przechowywane klasy, ich metody, a nawet całe aplikacje (włącznie ze stroną klienta, co nie było w tym artykule poruszane). Czysta przyjemność programowania w C++....dokończenle artykułu z strony 21 Podsumowanie Jak widać, podstawowa funkcjonalność, jaką Jest obsługa transakcji, bywa w różnych systemach mniej lub bardziej wyrafinowana. W każdym przypadku jest ona jednak wystarczająca, by zabezpieczyć się przed ewentualnym konfliktem w dostępie do danych. Pozostaje tylko korzystać z niej w umiejętny, świadomy sposób. 26 www.software.com.pl