CodeGym /Kursy /SQL SELF /Poziom izolacji SERIALIZABLE: pełna izolacj...

Poziom izolacji SERIALIZABLE: pełna izolacja i zapobieganie Phantom Read

SQL SELF
Poziom 40 , Lekcja 2
Dostępny

SERIALIZABLE — to najwyższy poziom izolacji transakcji w PostgreSQL. Ten poziom gwarantuje, że wyniki pracy równoległych transakcji będą takie same, jakby były wykonywane PO KOLEI, jedna po drugiej. Żadne anomalia równoległego wykonania (np. Dirty Read, Non-Repeatable Read, Phantom Read) nie mogą się wydarzyć.

Mówiąc prosto, SERIALIZABLE zapewnia pełny porządek i spójność między równoległymi transakcjami. To tak, jakby PostgreSQL mówił: "Wszyscy z transakcjami — do kolejki, panowie!"

Po co nam poziom SERIALIZABLE? Czasem chcesz mieć 100% pewności, że twoje dane zostają całkowicie spójne, mimo równoległych zmian. Wyobraź sobie scenę z supermarketu, gdzie kasjerzy obsługują klientów jednocześnie. Gdyby nikt nie pilnował kolejności, na wyjściu mogłoby być więcej towarów niż zostało kupionych. Z SERIALIZABLE taka sytuacja po prostu nie przejdzie.

Przykład ustawienia poziomu SERIALIZABLE

Żeby ustawić poziom izolacji SERIALIZABLE, użyj komendy:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Na przykład, stwórzmy transakcję, która używa tego poziomu:

BEGIN; -- Startujemy transakcję
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Ustawiamy poziom izolacji
SELECT * FROM products WHERE category = 'Electronics'; -- Pobieramy listę produktów
UPDATE products SET stock = stock - 1 WHERE product_id = 123; -- Aktualizujemy stan magazynowy
COMMIT; -- Zatwierdzamy zmiany

Case: rezerwacja biletów do kina

Sprawdźmy realny przykład sytuacji, gdzie poziom SERIALIZABLE jest po prostu niezbędny. Wyobraź sobie, że tworzysz system online do rezerwacji biletów do kina. Twoi użytkownicy wybierają miejsca i chcesz mieć pewność, że to samo miejsce nie zostanie kupione przez dwóch klientów jednocześnie.

Najpierw stwórzmy tabelę dla miejsc:

CREATE TABLE seats (
    seat_id SERIAL PRIMARY KEY,
    is_booked BOOLEAN DEFAULT FALSE
);

Teraz dodajmy kilka miejsc:

INSERT INTO seats (is_booked) VALUES (FALSE), (FALSE), (FALSE);

Pokażmy przykład transakcji z SERIALIZABLE.

Tak można zrobić bezpieczną rezerwację miejsca:

BEGIN; -- Start transakcji
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Poziom izolacji SERIALIZABLE

-- Sprawdzamy, czy miejsce jest wolne
SELECT is_booked FROM seats WHERE seat_id = 1;

-- Rezerwujemy miejsce
UPDATE seats SET is_booked = TRUE WHERE seat_id = 1;

COMMIT; -- Zatwierdzamy rezerwację

Przy próbie drugiej równoległej transakcji rezerwującej to samo miejsce PostgreSQL nie dopuści do zamieszania i rzuci błąd konfliktu serializacji.

Zapobieganie Phantom Read

Teraz rozkminimy "phantom read", od których tak bardzo chcieliśmy się uwolnić. Phantom Read pojawia się, gdy transakcja widzi zmiany danych dodanych przez inną transakcję w trakcie swojego działania. Na przykład, twoja transakcja oczekuje określonej liczby wierszy, ale nagle inna transakcja dodaje lub usuwa wiersze, zmieniając wyniki.

Zobaczmy przykład:

Dane przed startem transakcji

id balance user
1 1000 Alice
2 500 Bob

Transakcja 1

BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- Liczymy użytkowników z saldem większym niż 400
SELECT COUNT(*) FROM accounts WHERE balance > 400;

-- Oczekiwany wynik: 2 (Alice i Bob)

Transakcja 2

W innej sesji leci równoległa transakcja:

BEGIN;
INSERT INTO accounts (id, balance, user) VALUES (3, 700, 'Charlie');
COMMIT;

Wracamy do Transakcji 1

-- Powtarzamy zapytanie
SELECT COUNT(*) FROM accounts WHERE balance > 400;

Teraz, jeśli nie używasz SERIALIZABLE, wynik będzie 3 zamiast 2, bo Charlie został dodany w trakcie działania Transakcji 1. To właśnie Phantom Read.

Ale z SERIALIZABLE PostgreSQL gwarantuje, że Transakcja 1 nie zobaczy Charliego, bo jej "widok świata" jest zamrożony w momencie startu transakcji.

Cechy i ograniczenia poziomu SERIALIZABLE

Już wiemy, jak SERIALIZABLE pomaga osiągnąć idealną izolację. Ale co w tym świecie jest idealne bez wad? Pogadajmy szczerze.

Spadek wydajności
SERIALIZABLE wymaga dużo więcej zasobów niż poziomy READ COMMITTED czy REPEATABLE READ. Dlaczego? PostgreSQL musi emulować sekwencyjne wykonanie operacji, śledząc wszystkie możliwe konflikty między transakcjami.

Błędy serializacji
Jeśli PostgreSQL wykryje, że nie da się wykonać transakcji w "idealnej kolejności", generuje błąd serializacji (serialization_failure) i cofa transakcję.

Przykład błędu:

ERROR: could not serialize access due to concurrent update

Żeby sobie z tym radzić, możemy ponownie uruchomić transakcję po niepowodzeniu:

DO $$
DECLARE
    done BOOLEAN := FALSE;
BEGIN
    WHILE NOT done LOOP
        BEGIN
            -- Startujemy transakcję
            BEGIN;
            SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

            -- Wykonujemy operacje
            UPDATE accounts SET balance = balance - 100 WHERE id = 1;

            -- Zatwierdzamy zmiany
            COMMIT;
            done := TRUE; -- Wychodzimy z pętli, jeśli wszystko OK
        EXCEPTION WHEN serialization_failure THEN
            ROLLBACK; -- Cofamy przy błędzie
        END;
    END LOOP;
END;
$$;

To standardowe podejście w systemach, gdzie używasz SERIALIZABLE.

Ważne!

Ten kod jest napisany w PL-SQL. Wrócimy do niego później. Po prostu chciałem pokazać ci ładny i działający kod. No i pokazać, po co jest PL-SQL :)

Kiedy używać SERIALIZABLE?

Ten poziom izolacji ma sens tam, gdzie cena błędu jest bardzo wysoka:

  • Transakcje finansowe, jak obsługa płatności czy rozdzielanie bonusów.
  • Systemy zarządzania zapasami, żeby uniknąć podwójnych zamówień.
  • Rezerwacje online, gdzie ważne jest wykluczenie konfliktu przy rezerwacji zasobów.

Jeśli tworzysz system, gdzie dane muszą być w 100% spójne, a wydajność schodzi na drugi plan, SERIALIZABLE będzie twoim najlepszym kumplem.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION