W trakcie zapisu danych do bazy wiele rzeczy może pójść nie tak. Może zapełnić się dysk, połączenie sieciowe zostanie zerwane pomiędzy bazą a klientem, przewróci się aplikacja, która wysyła dane, lub inny proces będzie chciał nadpisać dane. 

Mamy wspólny mianownik rozwiązywania tych wszystkich problemów, a jest nim transakcja. W kontekście baz i systemów przechowywania danych transakcja to każda operacja traktowana jako pojedyncza jednostka pracy (grupuje operacje), która albo kończy się w pełni, albo nie kończy się w ogóle i pozostawia system w spójnym stanie. Sprawiają one, że bezpiecznie można powtórzyć operacje, są gwarantem bezpieczeństwa, a przynajmniej taką składają obietnicę.

Powszechnie gwaranty bezpieczeństwa transakcji rozpatruje się w ujęciu czterech właściwości, które nieprzypadkowo tworzą kwaśny akronim (ACID – Atomicity, Consistency, Isolation, Durability). Tak, jest w nim pewien zgrzyt, ale nie taki jak w jazzie – oczekiwany, dostrzegalny dopiero przy odpowiedniej dozie wrażliwości, powodujący gęsią skórę na potylicy i podróż, w której przewodnikiem jest muzyk. Kwas w tym akronimie jest toksyczny jak siarkowy (IV), jeszcze nie zabija, ale powoduje trwałe uszkodzenia – w postrzeganiu transakcji. Czas na rewizję tego skrótowca, by odrzucić toksyczne idee i destylować czyste zasady.

Atomicity (atomiczny)

Wykonanie przelewu bankowego wymaga dwóch operacji – nałożenie debetu na konto wysyłającego kasę oraz powiększenie salda odbiorcy. Nie może dojść do sytuacji, że tylko jedna część dojdzie do skutku. Te dwie operacje w naszym scenariuszu są nierozdzielne. Jeśli pierwsza część (redukcja salda) powiedzie się, a druga nie, to na koniec żądania chcemy, aby śladu po całej czynności nie było. To właśnie zapewnia nam pierwsza cecha transakcji – atomiczność.

Atomiczność oznacza, że każda transakcja albo zostanie wykonana w całości, albo w ogóle. Określa, co się wydarzy, kiedy klient chce zapisać kilka informacji, ale błąd pojawi się w połowie drogi – nie będzie to miało wpływu na dane. Jeśli zapisy są zgrupowane w atomiczną operację, to albo zapisze się wszystko, albo nic. Oznacza to, że bezpiecznie można powtórzyć operację, która nie skończyła się powodzeniem. Czy zatem tak właśnie nie powinna brzmieć nazwa tej właściwości – abortability (możliwy do przerwania)?

Consistency (spójny)

Obietnica spójności zapewnia, że po wykonaniu transakcji nie zostaną naruszone zasady integralności danych. Innymi słowy, że transakcja może przenieść bazę danych z jednego prawidłowego stanu do drugiego. Tyle że spójność danych w transakcji jest określona TYLKO przez możliwości bazy. Czego baza danych może pilnować? Tylko tego, co w niej możemy zdefiniować, czyli reguł i ograniczeń, takich jak: relacje klucz główny –  klucz obcy czy uruchomienie wyzwalaczy. 

Baza danych nie powstrzyma Cię przed jednoczesnym pomniejszeniem salda zarówno wysyłającego, jak i odbiorcy. To przecież od warstwy aplikacyjnej, gdzie logika biznesowa ma swoją implementację, zależy, czy dane będą spójne czy nie. Spójność danych – zapewnienie pewnych niezmienników – jest atrybutem aplikacyjnym. Zatem C do ACID nie pasuje.

Isolation (izolowalny)

Zwykle wielu klientów w tym samym czasie wykonuje operacje na bazie danych, co oznacza, że baza obsługuje współbieżnie swoich odbiorców. Nawet zgodnie ze sztuką, kiedy tylko jedna aplikacja ma dostęp do bazy, to będzie ona korzystała z wielu połączeń (sesji) lub posiadała wiele instancji przy horyzontalnym skalowaniu. Tak długo, jak aplikacje wykonują tylko odczyty, nic więcej do szczęścia nie potrzeba. 

Nie da się ukryć, że do bazy czasem wkładamy dane. Wtedy sprawy się komplikują, bowiem jedna operacja nie może wejść w paradę drugiej. Przykładowe scenariusze:

  • jedna transakcja aktualizuje wiersz, kiedy druga chce go odczytać. Ta druga nie może odczytać w połowie zaktualizowanej wartości, tylko odczytać starą, dopóki nowa w pełni nie zostanie zapisana;
  • pierwsza transakcja chce stworzyć zapis na podstawie odczytu innej wartości w bazie, druga transakcja nie może w tym czasie zmodyfikować tej pierwszej wartości, ponieważ spowoduje do rozsynchronizowanie stanu;
  • podczas tworzenia kopii zapasowej, kiedy chcemy zachować dostępność bazy, jej stan musi być spójny. Jednocześnie zapisy, które odbywają się w trakcie tworzenia kopii zapasowej, muszą zostać przyjęte, ale sama kopia zapasowa, która właśnie się tworzy, musi je zignorować.

Te i bardziej złożone problemy równoległego pisania i czytanie bazy rozwiązuje izolowalność transakcji, czyli udawanie, że transakcja jest jedyną operacją w całej bazie danych w danym momencie. Każde żądanie może wystąpić tak, jakby występowało jedno po drugim, nawet jeśli w rzeczywistości występuje jednocześnie.

Oczywiście jeśli baza danych obsługiwałaby transakcje liniowo, jedna po drugiej, bez współbieżności, wszystkie te komplikacje by zniknęły. Jak się domyślasz, odbyłoby się to wielkim wydajnościowym kosztem. 

Z tego powodu bazy danych mają właśnie mechanizmy i algorytmy pozwalające na równoczesne i bezkolizyjne wykonywanie wielu transakcji. Mamy wiele poziomów izolowalności i nie każda baza danych zapewnia pełną ochronę przed karambolem. Poziom izolacji w bazach danych określa, jakich anomalii możemy się spodziewać przy wykonywaniu transakcji. I w ACID zakłada pełną izolację transakcji, w praktyce jednak izolacja to spektrum. Im pełniejsza izolacja, tym większy narzut na dodatkowe mechanizmy i operacje pozorujące, że transakcja jest właśnie tą jedną wyjątkową i nic innego się w danej chwili nie liczy.

Durability (trwały)

Czwarta obietnica ACID mówi o tym, że jeśli cała transakcja została wykonana poprawnie, to jej efekt będzie trwały – dane nie zostaną zapomniane, nawet jeśli braknie prądu w serwerowni. A jeśli był błąd (a eufemistycznie mówiąc, jak to przystało na branżę IT: bug) w oprogramowaniu samej bazy danych i otrzymamy potwierdzenie poprawnego wykonania transakcji, ale tak naprawdę dane wyparowały? Co, jeśli dysk twardy zakończy swój żywot, a utworzenie kopii nie odbyło się po zapisaniu naszych danych? Tych przypadków Durability nie obsługuje. Trwałość w ACID ma bardzo konkretną definicję, która nie jest kuloodporna.

Dla nierozproszonej bazy danych (działającej na jednym serwerze) „trwały” oznacza, że dane zostaną zapisane na trwałym nośniku, co daje możliwość ich odzyskania po awarii. Taką obietnicę może realizować na przykład WAL (write ahead log), co pozwoli odzyskać dane nawet wtedy, kiedy zserializowana tabela zostanie uszkodzona. Dla rozproszonej bazy trwały może oznaczać, że dane zostały zreplikowane na kilka węzłów określonych przez kworum. Więcej o replikacji i kworum pisałem tutaj.

Podsumowanie

Z perspektywy aplikacji transakcje są warstwą abstrakcji, która pozwala udawać, że pewne problemy współbieżności oraz pewne rodzaje błędów sprzętowych nie istnieją. Kiedy aplikacja otrzyma błąd, wystarczy spróbować ponownie i istnieje duża szansa, że tym razem będziemy świętować zwycięstwo odczytu i zapisu. Dla tych wielu zalet ukuto marketingową etykietę. Jak to jednak bywa w reklamie, łatwo o nadużycia i brak wyjaśnienia niuansów.

W tym audycie najpierw z atomicity zrobiliśmy abortability, następnie z ACID – AID, a na końcu osłabiliśmy znaczenie trwałości. Mimo całej tej kastracji transakcje nadal są gwarantem bezpieczeństwa.

Źródła 

  1. Designing Data Intensive Applications – M. Kleppmann
  2. Databrics – ACID Transactions
  3. Wikipedia – ACID Transactions

Udostępnij ten wpis


Dobrnąłeś do końca. Jeśli ten artykuł był dla Ciebie wartościowy i chcesz otrzymywać informacje o kolejnych, to zapraszam Cię do zapisania się do listy mailingowej. Gwarantuję zero spamu.

Radek.

Inne artykuły

Partycjonowanie bazy danych okładka

Partycjonowanie bazy danych

Partycjonowanie pozwala podzielić tabelę na mniejsze części, gdzie każda z nich może się znajdować na innym serwerze. Zobacz, jak to działa oraz dlaczego jest to …

One thought on “Transakcje ACID

Comments are closed.