CodeGym /Kursy /SQL SELF /Praca z transakcjami dla zapewnienia integralności danych...

Praca z transakcjami dla zapewnienia integralności danych

SQL SELF
Poziom 22, Lekcja 2
Dostępny

Wyobraź sobie, że piszesz apkę dla sklepu internetowego i podczas płatności za zamówienie musisz:

  1. Zablokować kasę z karty klienta.
  2. Zmniejszyć ilość towaru w magazynie.
  3. Stworzyć wpis o udanej transakcji.

A co jeśli w trakcie tych działań coś pójdzie nie tak? Na przykład, towar w magazynie się skończył po tym, jak kasa została zablokowana, ale zanim powstał wpis o zamówieniu? Wszystko się sypie: kasa "wisi", zamówienie nie skończone, a twój serwer dostaje tony wkurzonych maili (i może nawet pozwy).

Transakcje właśnie pomagają uniknąć takich sytuacji. Pozwalają zgrupować kilka operacji w jedną "atomiczną" jednostkę pracy z bazą danych. To jak przycisk "Cofnij" w edytorze tekstu: jak coś pójdzie nie tak, po prostu wracasz do początku.

Jak transakcje zapewniają integralność danych?

Transakcje bazują na koncepcji ACID:

  • Atomowość (Atomicity) — Wszystkie operacje w transakcji wykonują się w całości albo wcale. "Wszystko albo nic".
  • Spójność (Consistency) — Dane są spójne przed i po wykonaniu transakcji.
  • Izolacja (Isolation) — Jedna transakcja nie przeszkadza innym.
  • Trwałość (Durability) — Jak transakcja się skończy, jej efekt zostaje nawet po awarii systemu.

Czemu znowu to powtarzam? Bo to taki ideał, do którego wszyscy dążą. I... który rzadko jest w pełni osiągalny. Jak drugi raz wrócimy do transakcji na naszym kursie, zobaczysz, że z niektórych zasad ACID będziemy musieli zrezygnować.

Więc korzystaj z czasu, kiedy transakcje są takie proste i piękne. No i przejdźmy już do przykładów!

Przykład użycia transakcji

Przeanalizujmy scenariusz dodania studenta i zapisania go na kurs.

Załóżmy, że pracujemy z bazą danych uniwersytetu. Pojawili się u nas wolni słuchacze naszych kursów. Jeśli na kursie jest miejsce, to takiego słuchacza rejestrujemy jako studenta (tymczasowo) i dodajemy na kurs. Tak to będzie wyglądać.

Dodając nowego studenta do bazy i zapisując go na kurs, musimy:

  1. Dodać wpis do tabeli students.
  2. Stworzyć wpis w tabeli enrollments, łączącej studenta z kursem.

Jeśli coś pójdzie nie tak (np. kurs już pełny), musimy cofnąć operację, żeby dane się nie rozjechały między tabelami. Tak to się robi:

-- Początek transakcji
BEGIN;

-- Krok 1: Dodajemy studenta
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- Załóżmy, że wróciło id = 10

-- Krok 2: Zapisujemy go na kurs
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 5);

-- Wszystko poszło OK? Zatwierdzamy zmiany
COMMIT;

Co się dzieje, jeśli pojawi się błąd?

Nagle pojawił się błąd przy zapisie na kurs: np. kurs nie istnieje. Jeśli zapomnisz o transakcji, to wpis o studencie w tabeli students zostanie, a w tabeli enrollments już nie. To psuje integralność danych. Żeby tego uniknąć, możemy użyć komendy ROLLBACK.

-- Początek transakcji
BEGIN;

-- Krok 1: Dodajemy studenta
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- Krok 2: Próbujemy zapisać go na kurs
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 999); -- Błąd: kurs o id = 999 nie istnieje!

-- Cofamy wszystkie zmiany
ROLLBACK;

W efekcie żadna z operacji się nie wykona i baza danych zostanie w tym samym stanie, co przed transakcją.

Użycie SAVEPOINT do kontroli

Wyobraź sobie teraz bardziej złożony scenariusz. Chcesz wykonać kilka operacji, ale w pewnym momencie musisz cofnąć się tylko do konkretnego punktu, a nie anulować cały proces.

Zróbmy krok po kroku rejestrację studenta

-- Początek transakcji
BEGIN;

-- Dodajemy studenta
SAVEPOINT add_student; -- Tworzymy punkt zapisu
INSERT INTO students (name, age, gender)
VALUES ('Anna Song', 22, 'Female');

-- Zapisujemy ją na pierwszy kurs
SAVEPOINT enroll_course_1; -- Kolejny punkt zapisu
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 5);

-- Zapisujemy ją na drugi kurs (tu błąd)
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 999); -- Błąd!

-- Cofamy się tylko do ostatniego punktu zapisu
ROLLBACK TO enroll_course_1;

-- Kontynuujemy proces
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 6);

-- Zatwierdzamy zmiany
COMMIT;

Dzięki temu błędy w jednej części procesu nie przeszkadzają w zapisaniu danych w innych.

Sprawdzanie, czy były zmiany

Jeśli zapytanie SQL coś zmienia, to można sprawdzić, czy faktycznie były jakieś zmiany, czy nie.

Może być tak, że wykonaliśmy DELETE, ale żadna linia nie pasowała do WHERE. Albo zrobiliśmy UPDATE, a dane już były zmienione i w sumie nic się nie zmieniło.

Na taką okazję jest specjalna systemowa zmienna FOUND. Pokazuje, czy jakieś linie zostały zmienione w ostatnim zapytaniu SQL:

  • FOUND = TRUE — zapytanie coś zaktualizowało/usunęło;
  • FOUND = FALSE — nic nie zostało usunięte ani zmienione.

Zwykły SELECT jej nie dotyczy, tylko do śledzenia zmian.

Zastosowanie praktyczne: obsługa płatności

Transakcje są szczególnie przydatne w aplikacjach finansowych. Znowu wróćmy do systemu, który ma przelać kasę z jednego konta na drugie.

-- Początek transakcji
BEGIN;

-- Krok 1: Zdejmujemy kasę z pierwszego konta
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;

-- Krok 2: Sprawdzamy, czy operacja się udała (linie zostały zmienione)
IF NOT FOUND THEN
    ROLLBACK; -- Cofka, jeśli środków za mało
    RAISE EXCEPTION 'Za mało środków!'; -- Błąd! Rzucamy wyjątek
END IF;

-- Krok 3: Dodajemy kasę na drugie konto
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

-- Zatwierdzamy transakcję
COMMIT;

Tu, jeśli klient próbuje przelać więcej kasy niż ma na koncie, transakcja się cofnie i baza nie będzie w "zawieszeniu".

Specyfika i typowe błędy

Zapomniany COMMIT: jeśli na końcu transakcji zapomnisz zrobić COMMIT, baza będzie "czekać", a zmiany się nie zapiszą.

Zapomniany WHERE: update albo delete bez warunku może skończyć się katastrofą. Na przykład, komenda DELETE FROM students bez WHERE usunie wszystkich studentów.

Długie transakcje: jeśli transakcja jest otwarta za długo, może blokować dostęp do danych, co prowadzi do problemów z wydajnością. Zawsze kończ transakcje (COMMIT albo ROLLBACK) jak najszybciej.

Transakcje — to twój jedyny ziomek, jeśli chodzi o zapewnienie integralności danych. Pomagają uniknąć niespójności, szczególnie w trudnych scenariuszach, jak rejestracja użytkowników, obsługa płatności czy update powiązanych tabel. Jak ogarniesz komendy BEGIN, COMMIT, ROLLBACK i SAVEPOINT, będziesz robić bardziej niezawodne i bezpieczne apki.

2
Zadanie
SQL SELF, poziom 22, lekcja 2
Niedostępne
Podstawy użycia transakcji
Podstawy użycia transakcji
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION