Transactions
Transakcje są fundamentalnym elementem każdego systemu zarządzania bazami danych. Kluczowym zadaniem mechanizmu transakcji jest wiązanie wielu kroków działań na bazie danych w jedną, „wszystko albo nic” operację. Pośrednie działania na bazie nie są widoczne dla konkurencyjnych bloków działań, dla konkurencyjnych transakcji. Jeżeli w toku działań wystąpi błąd, który uniemożliwi dokończenie transakcji, to żadne z działań, żaden z wcześniejszych kroków transakcji nie będzie miał wpływu na bazę danych, gdyż działania zostaną cofnięte.
Dla przykładu rozważmy bazę danych banku z bilansami kont klientów różnych typów (branż). Załóżmy, że chcemy dokonać przelew 100 zł z konta Alicji na konto Roberta. Komendy SQL mogłyby wyglądać jak poniżej
UPDATE konta SET bilans = bilans - 100.00
WHERE nazwa = 'Alicja';
UPDATE branże SET bilans = bilans - 100.00
WHERE nazwa = (SELECT branża_nazwa FROM konta WHERE nazwa = 'Alicja');
UPDATE konta SET bilans = bilans + 100.00
WHERE nazwa = 'Robert';
UPDATE branże SET bilans = bilans + 100.00
WHERE nazwa = (SELECT branża_nazwa FROM konta WHERE nazwa = 'Robert');
Powyższe operacje to 4 proste aktualizacje, które jednak powinny być wykonane razem lub wcale. Niedopuszczalne jest np. aby Robert otrzymał 100 zł bez obciążenia tą kwotą Alicji. Albo odwrotnie: Alicja zostałaby obciążona bez zasilenia konta Roberta. Potrzebujemy gwarancji, że jeżeli coś pójdzie źle na drodze działa, to żaden z kroków już wykonanych nie będzie miał wpływu na dane w bazie. Zapewniamy to grupując działania w bloku transakcji.
Mówi się, że transakcji jest atomowa, nie podzielna, z punktu widzenia innych transakcji. Zachodzi w całości lub wcale.
Z drugiej strony chcemy mieć gwarancje, ze zakończona z sukcesem transakcja da trwałe efekty i dokonane zmiany nie zostaną utracone w systemie zarządzania bazą danych, nawet gdy zatrzymanie systemu nastąpi wkrótce po zakończeniu transakcji. Transakcyjny system zarządzania bazą danych gwarantuje, że wszystkie aktualizacje poczynione przez transakcje są odnotowane w pamięci trwałej (np. na dysku) zanim transakcja zostanie zgłoszona jako wypełniona i zakończona.
Inną ważną cechą baz danych z obsługą transakcji jest właściwe postępowanie w sytuacji, gdy szereg transakcji wykonywanych jest jednocześnie. Żadna z transakcji nie powinna mieć możliwość podglądu nie kompletnych zmian poczynionych przez inne niezakończone transakcje. Na przykład, jeżeli jedna z transakcji jest zajęta podsumowywaniem uznań i obciążeń w ramach kont klientów z podziałem na branże jakie reprezentują, obciążenie konta Alicji nie będzie brane pod uwagę, gdy nie dokonano uznanie kwotą 100 zł konta Roberta, i vice versa. Aktualizacje poczynione do danego momentu przez niezakończone transakcje są niewidoczne dla innych działań, a gdy transakcja zakończy się z sukcesem efekty jej działań uwidocznione są jednocześnie.
W PostgreSQL, a transakcja jest ustanawiana przez otoczenie komend SQL klamrą słów BEGIN i COMMIT (rozpocznij i potwierdź). Czyli przykład powyżej wyglądałby następująco:
BEGIN;
UPDATE konta SET bilans = bilans - 100.00
WHERE nazwa = 'Alicja';
UPDATE branże SET bilans = bilans - 100.00
WHERE nazwa = (SELECT branża_nazwa FROM konta WHERE nazwa = 'Alicja');
UPDATE konta SET bilans = bilans + 100.00
WHERE nazwa = 'Robert';
UPDATE branże SET bilans = bilans + 100.00
WHERE nazwa = (SELECT branża_nazwa FROM konta WHERE nazwa = 'Robert');
COMMIT;
Jeżeli, w którymś kroku wykonywania transakcji zdecydujemy się, że nie chcemy jej dalej kontynuować i chcemy ją przerwać i cofnąć dotychczasowe działania, wywołujemy komendę ROLLBACK (przewiń do tyłu, cofnij) zamiast COMMIT, np. okazało się, że Alicja nie ma nawet 100 zł na koncie i nie można dokonać przelewu.
PostgreSQL tak naprawdę każą kwerendę SQL uruchamia wewnątrz domyślnej, niejawnej transakcji BEGIN ... COMMIT. Dla odróżnienia od transakcji jawnie definiowanych od niejawnych nazywa się te pierwsze blokami transakcji, co jest o tyle słuszne, że zazwyczaj transakcje jawne obejmują kilka kwerend.
PostgreSQl daje możliwość bardziej szczegółowego kontrolowania transakcji poprzez użycie punktów bezpieczeństwa (savepoints). SAVEPOINT pozwala w selektywny sposób wybrać części transakcji do odrzucenia, a zatwierdzić resztę. Po zdefiniowaniu punktu bezpieczeństwa SAVEPOINT możemy w razie potrzeby cofnąć transakcję do tego miejsca używając komendy ROLLBACK TO. Wszystkie działania na bazie danych pomiędzy punktem bezpieczeństwa a ROLLBACK TO go wywołującym są odrzucone ale zmiany poczynione wcześniej niż ustawiony punkt bezpieczeństwa są utrzymane i mają szansę zostać zatwierdzone.
Ustawiony punkt bezpieczeństwa może być wykorzystany kilkakrotnie w ramach bloku transakcji, tzn. możemy cofać się do niego nie tylko jeden raz.
Wracając do naszego przykładu przypuśćmy, że chcemy obciążyć konto Alicji i zasilić tą kwotą konto odpowiedniej osoby. Załóżmy, że zakładamy możliwość pomyłki, tak jak poniżej. Po zasileniu konta Roberta uświadamiamy sobie, że to konto Andrzeja miało zostać zasilone.
BEGIN;
UPDATE konta SET bilans = bilans - 100.00
WHERE nazwa = 'Alicja';
SAVEPOINT my_savepoint;
UPDATE konta SET bilans = bilans + 100.00
WHERE nazwa = 'Robert';
-- pomyłka, to konta Andrzeja miało być zasilone
ROLLBACK TO my_savepoint;
UPDATE konta SET bilans = bilans + 100.00
WHERE nazwa = 'Andrzej';
COMMIT;
Powyższy przykład jest zbytnim uproszczeniem. Ale w rzeczywistości nie mamy innego narzędzia niż SAVEPOINT i ROLLBACK TO, aby w sytuacji błędu systemowego w jakikolwiek inny sposób próbować odzyskać kontrolę nad blokiem transakcji i nie tracić wszystkich zmian i nie zaczynać zupełnie od nowa.