Podcast o Transakcje bazodanowe, SQL i architektury
Transakcje bazodanowe, SQL i Architektury – Kompletny Przewodnik
Podcast
Transakcje bazodanowe, SQL i architektury
Délka: 15 minut
Přepis
Kristýna: I właśnie dlatego to wszystko jest takie genialne! Albo robi się wszystko, albo nic. Żadnych półśrodków.
Martin: Czekaj, czyli jak wysyłam pieniądze w bankowości internetowej, to ta operacja, to jest dokładnie to? Albo pieniądze wyjdą z mojego konta i trafią na drugie, albo po prostu nic się nie stanie i pieniądze zostaną u mnie. Nic pomiędzy?
Kristýna: Dokładnie tak! Trafiłeś w sedno. To jest serce całej koncepcji transakcji.
Martin: To świetnie! Okej, to musimy rozebrać od początku. Słuchacie Studyfi Podcast, a dzisiaj zanurzymy się w świat transakcji bazodanowych.
Kristýna: Więc, Martin, twoja analogia z bankiem była perfekcyjna. Transakcja to w zasadzie grupa operacji, które logicznie do siebie pasują i muszą być wykonane jako jedna całość. Jedna nierozłączna jednostka.
Martin: Jak jeden atom, którego nie da się dalej dzielić?
Kristýna: Dokładnie! A to prowadzi nas do pierwszej i najważniejszej właściwości transakcji. Nazywa się ją atomowością. Wyobraź sobie, że chcesz przelać tysiąc złotych. Co musi się stać?
Martin: No cóż... z mojego konta musi zostać odjęty tysiąc, a na konto kolegi musi zostać dodany tysiąc. To są dwie operacje.
Kristýna: Zgadza się. A atomowość gwarantuje, że albo obie się wydarzą, albo żadna. Gdyby system padł po tym, jak pieniądze zostały odjęte z twojego konta, ale jeszcze zanim zostały dopisane koledze... to byłby problem, prawda?
Martin: To byłby duży problem! Straciłbym pieniądze, a on by ich nie dostał.
Kristýna: I właśnie dlatego jest transakcja. Jeśli jakakolwiek jej część się nie powiedzie, cała transakcja zostanie wycofana. Nazywa się to ROLLBACK. Baza danych zostanie przywrócona do stanu, w jakim była PRZED rozpoczęciem transakcji. Jakby nigdy się to nie wydarzyło.
Martin: Czyli transakcja to takie zabezpieczenie, które utrzymuje bazę danych w spójnym, czyli poprawnym i logicznym, stanie. Przenosi ją z jednego dobrego stanu do kolejnego dobrego stanu.
Kristýna: Dokładnie. Spójność jest kluczowa. Bez niej w danych panowałby chaos.
Martin: Dobrze, atomowość rozumiem. Wszystko albo nic. Co dalej? Jakie inne supermoce mają transakcje?
Kristýna: Supermoce, to mi się podoba. Kolejna to izolacja. To oznacza, że jedna trwająca transakcja nie widzi bałaganu, który robi inna, równolegle działająca transakcja.
Martin: Co masz na myśli, mówiąc 'bałagan'?
Kristýna: Wyobraź sobie, że dwie transakcje pracują na tych samych danych. Gdyby jedna widziała niedokończone, jeszcze niepotwierdzone zmiany tej drugiej, mogłaby pracować na błędnych, tymczasowych danych. Izolacja zapewnia, że każda transakcja ma wrażenie, że jest w bazie danych sama.
Martin: Aha, czyli nie wchodzą sobie w drogę. A co się stanie, gdy jedna transakcja musi zostać wycofana, czyli zrobić ten ROLLBACK? Czy to nie wpłynie na pozostałe?
Kristýna: W idealnym świecie nie. Ale gdyby jedna transakcja pracowała na danych, które inna transakcja zmieniła, a potem wycofała, to ta pierwsza też musiałaby zostać wycofana. Nazywa się to wycofaniem łańcuchowym albo efektem domina. To jest coś, czemu systemy starają się zapobiegać.
Martin: Efekt domina brzmi... drogo.
Kristýna: Może być. Trzecią właściwością jest trwałość, czyli po angielsku durability. To proste – gdy tylko transakcja zostanie pomyślnie zakończona i potwierdzona poleceniem COMMIT, jej zmiany są zapisywane w bazie danych na stałe. Przeżyją nawet przerwę w dostawie prądu lub restart serwera.
Martin: Czyli COMMIT to ta ostateczna pieczęć: „Gotowe, zapisane na zawsze.”
Kristýna: Dokładnie tak. A ostatnia właściwość to szeregowalność, która jest ważna głównie w przypadku rozproszonych baz danych. Upraszczając, mówi, że nawet jeśli wiele transakcji działa jednocześnie i przeplata się, wynik musi być taki sam, jak gdyby zostały wykonane ładnie jedna po drugiej w jakiejś kolejności.
Martin: Wspomniałaś o rozproszonych bazach danych. To brzmi jak coś z Gwiezdnych Wojen. Co to jest?
Kristýna: To w zasadzie całkiem powszechne. Wyobraź sobie dużą firmę z oddziałami na całym świecie. Baza danych nie znajduje się na jednym superkomputerze, ale jest podzielona na mniejsze części, fragmenty, które są przechowywane na różnych serwerach, nazywamy je węzłami.
Martin: Czyli dane z Pragi są na serwerze w Pradze, a dane z Nowego Jorku na serwerze w Nowym Jorku?
Kristýna: Dokładnie. A teraz wyobraź sobie, że chcesz wykonać operację, która dotyczy danych w obu miastach. Na przykład przenieść pracownika z Pragi do Nowego Jorku. To jest transakcja globalna.
Martin: Ponieważ wykracza poza jeden węzeł.
Kristýna: Tak. A ta globalna transakcja rozpada się na cząstkowe, lokalne transakcje. Jedna lokalna transakcja odbędzie się w Pradze – usunie pracownika. Druga w Nowym Jorku – doda go. I to wszystko musi działać atomowo. Albo obie się powiodą, albo żadna.
Martin: A kto tym zarządza? Musi być jakiś szef, prawda?
Kristýna: Oczywiście. Węzeł, z którego transakcja startuje, staje się węzłem głównym. I tam działa tak zwana transakcja główna, która koordynuje wszystkie te cząstkowe części. To taki dyrygent orkiestry.
Martin: A ten dyrygent ma do pomocy jakichś pomocników?
Kristýna: Ma. W każdym węźle jest moduł zarządzania transakcjami, który analizuje żądania, rozdziela pracę, a potem składa wyniki w całość. A potem jest moduł zarządzania danymi, który dba o samo czytanie i zapisywanie w lokalnej bazie danych.
Martin: Dobrze, czyli mamy dyrygenta – transakcję główną – i orkiestrę transakcji lokalnych. Ale jak dyrygent zapewni, że wszyscy zagrają poprawnie i w tym samym momencie? Zwłaszcza gdy są na przykład po drugiej stronie planety.
Kristýna: Świetne pytanie. Używa się do tego czegoś, co nazywa się dwufazowym protokołem zatwierdzania. Brzmi to skomplikowanie, ale zasada jest elegancka. Działa to w dwóch fazach.
Martin: Faza pierwsza?
Kristýna: Faza pierwsza to faza głosowania. Transakcja główna pyta wszystkie cząstkowe transakcje: „Czy jesteście gotowe zatwierdzić zmiany? Czy zakończyłyście swoją pracę i nie widzicie żadnego problemu?” Każda cząstkowa transakcja wykonuje swoją pracę, ale zmian jeszcze finalnie nie zapisuje. Tylko je przygotowuje i odpowiada „Tak, jestem gotowa” albo „Nie, wystąpił błąd”.
Martin: Czyli wszyscy tylko przygotowują ołówek nad kartką, ale jeszcze nic nie podpisują.
Kristýna: Dokładnie! A teraz nadchodzi faza druga – faza decyzji. Jeśli WSZYSTKIE cząstkowe transakcje odpowiedziały „Tak, jestem gotowa”, transakcja główna wysyła globalne polecenie COMMIT. Dopiero wtedy wszyscy naraz „podpisują” i trwale zapisują zmiany.
Martin: A co jeśli choćby jedna jedyna odpowie „Nie”? Albo w ogóle nie odpowie, bo na przykład padł serwer?
Kristýna: Wtedy transakcja główna wysyła globalne polecenie ABORT. I wszyscy, nawet ci, którzy byli gotowi, odrzucają swoje zmiany. Cała operacja zostaje wycofana. To naprawdę albo wszyscy, albo nikt.
Martin: To jest całkiem solidne. Ale co się stanie, gdy połączenie zostanie przerwane akurat w tej drugiej fazie? Na przykład węzeł lokalny czeka na polecenie, a węzeł główny padnie.
Kristýna: To są właśnie te najbardziej podstępne sytuacje. Systemy mają na to mechanizmy, takie jak limit czasu, tak zwany time out. Jeśli odpowiedź nie nadejdzie na czas, transakcja zostaje prewencyjnie anulowana. Rozwiązanie tych awarii to jedna z najtrudniejszych części systemów baz danych.
Martin: Mówiliśmy o tym, że transakcje działają izolowanie, jakby były same w bazie danych. Ale w rzeczywistości nie są. Jak system sprawi, żeby dwie transakcje, które chcą zmienić ten sam wiersz w tabeli, nie wchodziły sobie w drogę?
Kristýna: Używa do tego blokad. To jak klucz do sali konferencyjnej. Gdy transakcja chce pracować z jakimś obiektem – na przykład wierszem w tabeli – musi go najpierw zablokować. Dopóki go nie odblokuje, nikt inny nie może z nim pracować.
Martin: To ma sens. Czyli gdy ja chcę zmienić status zamówienia, blokuję go. A gdy w tym samym momencie spróbuje go zmienić ktoś inny, jego transakcja musi poczekać, aż ja skończę i odblokuję.
Kristýna: Dokładnie. Nazywa się to dwufazowym protokołem blokowania. W pierwszej fazie transakcja tylko nabywa blokady, a w drugiej fazie już tylko je zwalnia. Ale co myślisz, że może się stać, gdy dwie transakcje chcą zablokować wzajemnie więcej rzeczy?
Martin: Hmm... Załóżmy, że transakcja A blokuje obiekt 1 i czeka na obiekt 2. A transakcja B blokuje obiekt 2 i czeka na obiekt 1.
Kristýna: I mamy problem! Każda czeka na drugą i żadna nie może ruszyć z miejsca. Nazywa się to zakleszczeniem, po angielsku deadlock.
Martin: Jak dwoje ludzi, którzy spotykają się w wąskiej uliczce i każdy czeka, aż ten drugi ustąpi.
Kristýna: Idealne porównanie! A system musi umieć to rozpoznać. Robi to za pomocą tak zwanych grafów oczekiwania. Transakcje to węzły, a strzałka od A do B oznacza „A czeka na B”. Jeśli w tym grafie powstanie okrąg – A czeka na B, B czeka na A – mamy zakleszczenie.
Martin: A co system z tym zrobi? Musi jednego z tych dwóch ludzi w uliczce odepchnąć, prawda?
Kristýna: Dokładnie. Jedną z transakcji po prostu „zabija”. To znaczy, że ją przerywa, wykonuje ROLLBACK jej zmian i zwalnia jej blokady. W ten sposób krąg zostaje przerwany, a druga transakcja może kontynuować. Ta anulowana zazwyczaj próbuje się wykonać ponownie.
Martin: Czyli system pozwala na zakleszczenie, a potem je rozwiązuje. Czy nie dałoby się mu jakoś zapobiec?
Kristýna: Da się. To inna strategia. Zamiast wykrywania i rozwiązywania, możemy próbować unikać zakleszczeń. Jedną z popularnych metod są znaczniki czasu. Każda transakcja na starcie otrzymuje unikalny znacznik czasu – w zasadzie numer, który mówi, kiedy się rozpoczęła. Im mniejszy numer, tym „starsza” transakcja.
Martin: A do czego to jest dobre?
Kristýna: Gdy dochodzi do konfliktu o blokadę, system patrzy na ich znaczniki czasu i decyduje według z góry ustalonych zasad. Istnieją dwie główne metody: „Wait-Die” i „Wound-Wait”.
Martin: Czekaj-Giń i Rań-Czekaj? To brzmi ostro.
Kristýna: Nazwy są dramatyczne, ale zasady logiczne. W przypadku „Wait-Die” obowiązuje zasada: jeśli starsza transakcja chce blokadę, którą trzyma młodsza transakcja, to starsza czeka (Wait). Ale jeśli młodsza transakcja chce blokadę od starszej, to młodsza jest natychmiast kończona (Die) i musi spróbować ponownie później.
Martin: Czyli młodsi ustępują starszym. To uprzejme.
Kristýna: Dokładnie tak. A w przypadku „Wound-Wait” jest to na odwrót, aktywne ze strony starszej. Jeśli starsza transakcja chce blokadę, którą trzyma młodsza, to tę młodszą „rani” (Wound) – czyli zmusza ją do ROLLBACKu – i zabiera blokadę. Jeśli młodsza chce blokadę od starszej, musi pokornie czekać (Wait).
Martin: W obu przypadkach starsza transakcja ma pierwszeństwo. Różni się tylko to, kto jest aktywny. Rozumiem.
Martin: Dobrze, teorię mamy. Ale jak to wygląda w praktyce? Kiedy piszę kod, jak powiem bazie danych: „Hej, teraz zaczyna się transakcja, a tutaj się kończy”?
Kristýna: To prostsze, niż myślisz. W języku SQL, który jest używany do komunikacji z większością baz danych, transakcja często zaczyna się automatycznie pierwszym poleceniem, które zmienia dane.
Martin: A jak ją zakończę?
Kristýna: Masz dwie możliwości. Jeśli wszystko przebiegło pomyślnie i chcesz trwale zapisać zmiany, na końcu wpisujesz polecenie COMMIT.
Martin: To ta ostateczna pieczęć, o której mówiliśmy.
Kristýna: Tak. A jeśli coś poszło nie tak lub z jakiegoś powodu chcesz anulować wszystkie zmiany od początku transakcji, użyjesz polecenia ROLLBACK.
Martin: Czyli albo COMMIT, albo ROLLBACK. Wszystko albo nic. Znowu do tego wracamy.
Kristýna: Dokładnie. Istnieje jeszcze jedna przydatna rzecz, a jest nią SAVEPOINT. Podczas długiej transakcji możesz tworzyć takie punkty kontrolne. Możesz napisać SAVEPOINT moj_punkt;. A gdy potem wykonasz ROLLBACK TO moj_punkt;, nie zostanie wycofana cała transakcja, ale tylko zmiany dokonane po tym punkcie.
Martin: To jak zapisywanie pozycji w grze wideo! Gdy popełnię błąd, nie muszę zaczynać od samego początku, ale tylko od ostatniego punktu kontrolnego.
Kristýna: To jest absolutnie doskonała analogia! Dokładnie tak to działa. Warto jednak wiedzieć, że ROLLBACK do savepointu nie zwalnia tak zwanych dzienników, czyli plików logów. Tylko pełny ROLLBACK je czyści.
Martin: Wspomniałaś o dziennikach. To te pliki, gdzie baza danych zapisuje, co się dzieje, na wypadek awarii?
Kristýna: Tak. Dziennik, czyli log, jest absolutnie kluczowy dla odzyskiwania. To w zasadzie dzienniczek, do którego baza danych starannie zapisuje każdy krok każdej transakcji: kiedy się rozpoczęła, jakie dane zmienia – starą i nową wartość – i kiedy się zakończyła, czy to poleceniem COMMIT, czy ABORT.
Martin: Czyli gdy serwer padnie, a potem zostanie ponownie uruchomiony, pierwszą rzeczą, jaką zrobi, jest przeczytanie tego dzienniczka?
Kristýna: Dokładnie tak. Patrzy do niego i sprawdza, które transakcje były w trakcie pracy w momencie awarii. Używa do tego tak zwanego algorytmu UNDO/REDO. Tworzy dwie listy: listę UNDO i listę REDO.
Martin: Co jest na tych listach?
Kristýna: Na listę REDO umieszcza wszystkie transakcje, które zdążyły wykonać COMMIT przed awarią, ale których zmiany być może jeszcze nie zdążyły zostać fizycznie zapisane na dysku. Te transakcje system wykonuje ponownie (REDO), aby mieć pewność, że dane są kompletne.
Martin: A na listę UNDO?
Kristýna: Tam trafiają wszystkie transakcje, które w momencie awarii nie były zakończone. W ich przypadku system bierze wszystkie ich dokonane zmiany i cofa je jedna po drugiej (UNDO). W ten sposób zapewnia, że w bazie danych nie pozostanie żadna połowiczna, niespójna praca.
Martin: Czyli dzięki dziennikowi system po katastrofie potrafi posprzątać bazę danych i przywrócić ją do ostatniego znanego poprawnego stanu. To fascynujące.
Kristýna: To jest kamień węgielny niezawodności baz danych. Bez tego po prostu by to nie działało.
Martin: Wow, Krystyno, to było mnóstwo informacji. Spróbujmy to na koniec przejrzeć. Transakcja to grupa operacji, która wykonuje się jako jedna całość – albo wszystko, albo nic. Nazywa się to atomowością.
Kristýna: Zgadza się. Dalej musi być izolowana, żeby równoległe transakcje nie wchodziły sobie w drogę. Jej wyniki muszą być po potwierdzeniu (COMMIT) trwałe, a wynik równoległego przetwarzania musi odpowiadać jakiemuś szeregowemu wykonaniu.
Martin: W rozproszonych bazach danych mamy transakcje globalne, które zarządzają tymi lokalnymi za pomocą dwufazowego zatwierdzania. A żeby transakcje nie ingerowały w te same dane, używają blokad.
Kristýna: Co może prowadzić do zakleszczenia (deadlocku), które system albo wykrywa i rozwiązuje, albo stara się go unikać za pomocą znaczników czasu.
Martin: A w SQL wszystko kontrolujemy głównie poleceniami COMMIT do trwałego zapisu i ROLLBACK do anulowania zmian. A na wypadek katastrofy baza danych ma dziennik, dzięki któremu potrafi się odzyskać.
Kristýna: Idealne podsumowanie! Poradziłeś sobie na szóstkę. Myślę, że teraz już każdemu jest jasne, dlaczego transakcje są tak niezastąpione.
Martin: Zdecydowanie. Wielkie dzięki, Krystyno, za świetne wyjaśnienie. I dziękujemy wam, że słuchaliście Studyfi Podcast. W kolejnym odcinku do usłyszenia!
Kristýna: Do usłyszenia!