Przetwarzanie transakcyjne — ACID, współbieżność i izolacja

Jak działa indeks?

Indeks B-tree pozwala znaleźć dane bez przeglądania całej tabeli. Wyszukiwanie zaczyna się od korzenia: porównujemy szukaną wartość, np. nazwisko „Nowak”, z granicami zakresów. Kolejne węzły wewnętrzne zawężają zakres, aż trafimy do liścia z kluczem i odwołaniami do pasujących wierszy tabeli. Wszystkie liście są na tej samej głębokości; węzeł może wskazywać wiele zakresów, więc nie jest to drzewo binarne.

Film pokazuje różnicę między skanowaniem tabeli a wyszukiwaniem przez indeks. To ilustracja algorytmu, nie pomiar czasu PostgreSQL. Indeks wymaga miejsca i utrzymania przy zapisie; przy odczycie większości tabeli skan może być tańszy.

Cel ostatniego wykładu

Na poprzednim spotkaniu programowaliśmy w bazie: zmienne, warunki, pętle, funkcje i wyjątki w PL/pgSQL. Teraz pytamy: co się stanie, gdy dwie osoby zmienią te same dane albo jedna z operacji zakończy się błędem?

Pozostajemy przy PostgreSQL 16 i pgAdmin. Na jednym przykładzie magazynowym prześledzimy zatwierdzanie i wycofywanie zmian, punkty zapisu, widoczność danych, blokady oraz ponawianie transakcji. To ostatni wykład; pytania na końcu służą wspólnemu podsumowaniu materiału, a nie testowi początkowemu.

1. Przygotowanie pokazu — dwa magazyny, ten sam towar

Pobierz kompletny zestaw SQL.

W środowisku kursu po aktualizacji wykonaj docker compose up -d w katalogu docker. Otwórz bazę rsod5_pokaz i folder materials/lectures/wyklad5. Dane zostaną utworzone automatycznie w osobnym schemacie rsod5_demo. Pokaz nie zmienia tabel z wykładu 4. Jeśli przygotowujesz pustą bazę ręcznie, wykonaj 00_setup.sql jeden raz.

W obu magazynach przechowujemy ten sam typ radiotelefonu:

warehouse_id quantity
A 5
B 0

warehouse_id to klucz główny, a quantity ma ograniczenia NOT NULL i CHECK (quantity >= 0). W pierwszym pokazie przenosimy 2 sztuki z A do B: poprawny stan końcowy to 3 i 2, suma nadal wynosi 5.

Włącz Auto commit w pgAdmin. Pojedyncze polecenia poza jawnym blokiem są wtedy zatwierdzane osobno; BEGIN otwiera transakcję, którą kończy dopiero COMMIT albo ROLLBACK. Cały blok jednoseryjnego pokazu zaznaczaj razem. Wyjątkiem są celowe błędy i pokazy A/B — tam wykonuj wskazane kroki osobno.

2. Transakcja: jedna operacja użytkownika, kilka poleceń

Przeniesienie towaru obejmuje dwa zapisy: odejmowanie w A i dodawanie w B. Gdyby każde UPDATE było zatwierdzane osobno, awaria między nimi pozostawiłaby bazę w stanie niezgodnym z regułą operacji magazynowej. Transakcja wyznacza granicę: zatwierdzamy wszystkie jej zmiany albo żadnej.

ACID na naszym przykładzie

Właściwość Znaczenie w pokazie
Atomicity — atomowość Nie zatwierdzamy samego odjęcia bez dodania. ROLLBACK cofa oba zapisy.
Consistency — spójność Ograniczenia tabel i poprawna logika operacji mają zachować reguły danych. Ujemna ilość jest zabroniona.
Isolation — izolacja Poziom izolacji i mechanizmy współbieżności określają, jakie skutki równoległych transakcji są dopuszczalne.
Durability — trwałość Zatwierdzone zmiany są utrwalane zgodnie z konfiguracją trwałości serwera; standardowo PostgreSQL wykorzystuje dziennik WAL.
ImportantBaza nie zna reguły, której nie zapisaliśmy

CHECK (quantity >= 0) nie pilnuje sumy ilości w obu magazynach. Transakcja zawierająca tylko odjęcie i COMMIT może być poprawna dla ograniczeń tabeli, a błędna biznesowo. Atomowość nie naprawia błędnej logiki. W rzeczywistej aplikacji sprawdzamy także, czy oba UPDATE zmieniły oczekiwaną liczbę wierszy, i wycofujemy całą operację przy niespełnionych warunkach.

Pokaz 1 — obejrzyj zmiany i wycofaj

-- Start at A=5, B=0. Move two radios, inspect, then undo the entire transfer.
BEGIN;
UPDATE rsod5_demo.stock SET quantity = quantity - 2 WHERE warehouse_id = 'A';
UPDATE rsod5_demo.stock SET quantity = quantity + 2 WHERE warehouse_id = 'B';
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;
ROLLBACK;
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;

Pierwszy SELECT w tej samej transakcji widzi własne zmiany: A=3, B=2. Po ROLLBACK drugi odczyt daje A=5, B=0. Nie cofamy całej historii bazy, lecz tylko niezatwierdzone zmiany bieżącej transakcji.

Pokaz 2 — zatwierdź przeniesienie

-- Start at A=5, B=0. Commit the transfer: A=3, B=2, total=5.
BEGIN;
UPDATE rsod5_demo.stock SET quantity = quantity - 2 WHERE warehouse_id = 'A';
UPDATE rsod5_demo.stock SET quantity = quantity + 2 WHERE warehouse_id = 'B';
COMMIT;
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;

Po COMMIT stan to A=3, B=2. Kolejne ROLLBACK nie cofnie już tego zatwierdzenia. Potrzebna byłaby nowa operacja kompensująca, np. przeniesienie towaru z powrotem. Trwałość nie zastępuje kopii zapasowych ani ochrony przed błędnym poleceniem zatwierdzonym przez użytkownika.

3. SAVEPOINT — cofnij część pracy

Zaczynamy od stanu 3,2 po pokazie 2. Chcemy przenieść jedną sztukę, ale przy dodawaniu omyłkowo wpisujemy 99. Punkt zapisu pozwala cofnąć część operacji bez utraty wcześniejszego odjęcia.

-- Start after 02: A=3, B=2. Correct a mistaken increase without losing the debit.
BEGIN;
UPDATE rsod5_demo.stock SET quantity = quantity - 1 WHERE warehouse_id = 'A';
SAVEPOINT after_debit;
UPDATE rsod5_demo.stock SET quantity = quantity + 99 WHERE warehouse_id = 'B';
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;
ROLLBACK TO SAVEPOINT after_debit;
UPDATE rsod5_demo.stock SET quantity = quantity + 1 WHERE warehouse_id = 'B';
COMMIT;
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;

Kolejne stany to 2,2 po odjęciu, 2,101 po błędnym dodaniu, 2,2 po ROLLBACK TO, a na końcu 2,3 po poprawce i COMMIT. Stan pośredni 2,101 jest widoczny we własnej transakcji, ale nie zostaje zatwierdzony.

SAVEPOINT nie zatwierdza wcześniejszych zmian. Pełne ROLLBACK nadal wycofałoby całą transakcję. ROLLBACK TO SAVEPOINT zachowuje transakcję otwartą, a COMMIT kończy ją i zatwierdza pozostałe zmiany.

Co po błędzie SQL?

Plik 04_error.sql zawiera dwa celowe błędy. Wykonuj kroki osobno:

-- Run marked steps separately. Expected SQLSTATEs: 23514, then 25P02.
-- STEP 1: start a transaction and trigger CHECK failure.
BEGIN;
UPDATE rsod5_demo.stock SET quantity = -1 WHERE warehouse_id = 'A';
-- STEP 2: this SELECT fails because the transaction is aborted.
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;
-- STEP 3: recover the session. State stays A=2, B=3 if following 01-03.
ROLLBACK;
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;

Pierwszy błąd to naruszenie CHECK (23514). Drugi (25P02) informuje, że transakcja jest w stanie przerwanym — następny SELECT nie jest naprawą. ROLLBACK przywraca sesję do pracy, a dane pozostają 2,3. Jeżeli przed błędnym poleceniem utworzono punkt zapisu, można użyć ROLLBACK TO SAVEPOINT, a następnie kontynuować transakcję.

W PL/pgSQL blok z EXCEPTION pozwala obsłużyć błąd i wycofuje zmiany wykonane wewnątrz chronionego bloku. To nie jest zatwierdzenie transakcji. Zwykła funkcja wywołana przez SELECT działa w transakcji wywołującego; nie umieszczamy w niej COMMIT. Granicę transakcji ustalamy świadomie.

4. Przygotuj dwie niezależne sesje

Otwórz dwa niezależne Query Tool dla tej samej bazy rsod5_pokaz. Nazwij je umownie A i B. Dwie karty edytora współdzielące jedno połączenie nie wystarczą. SELECT pg_backend_pid(); powinien dawać dwa różne numery.

Przed każdym pokazem 5–10:

  1. Zakończ wcześniejsze transakcje w obu sesjach (COMMIT albo ROLLBACK).
  2. W jednej sesji uruchom 99_reset.sql: wracamy do A=5, B=0.
  3. Otwórz odpowiednie pliki A/B i zaznaczaj wyłącznie wskazany krok.

Nie resetuj danych, gdy druga sesja utrzymuje blokadę — reset też jest zapisem i może czekać. W pokazach blokowania ustawiamy limit 60 sekund. Po jego przekroczeniu wycofaj transakcje, zresetuj dane i powtórz demonstrację.

5. READ COMMITTED — świeży obraz dla każdego polecenia

To domyślny poziom izolacji PostgreSQL. Odczyt widzi dane zatwierdzone przed rozpoczęciem danego polecenia oraz własne wcześniejsze zmiany. Nowe polecenie w tej samej transakcji może zobaczyć nowsze zatwierdzone dane.

Sesja A — plik 05_read_committed_A.sql

-- Reset first with 99_reset.sql, then use TWO independent connections.
-- A1: run this block; A reads 5.
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT pg_backend_pid() AS session_a;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- A2: run after B1 (B has updated but NOT committed); A still reads 5.
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- A3: run after B2 (COMMIT); A now reads 4 in the SAME transaction.
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
COMMIT;

Sesja B — plik 05_read_committed_B.sql

-- B1: run after A1. Leave this transaction OPEN, then return to A2.
BEGIN;
SELECT pg_backend_pid() AS session_b;
UPDATE rsod5_demo.stock SET quantity = quantity - 1 WHERE warehouse_id = 'A';
-- B2: run after A2. Then return to A3.
COMMIT;
Kolejność Sesja A Sesja B Odczyt A
A1 BEGIN, pierwszy SELECT — 5
B1 — BEGIN, odjęcie 1, bez zatwierdzenia —
A2 drugi SELECT nadal otwarta transakcja 5
B2 — COMMIT —
A3 trzeci SELECT, potem COMMIT — 4

A nie zobaczyła niezatwierdzonej czwórki: nie było brudnego odczytu. Po zatwierdzeniu B odczyt w tej samej transakcji A się zmienił: zaobserwowaliśmy niepowtarzalny odczyt.

Zwykły SELECT nie musi czekać na koniec takiej aktualizacji: PostgreSQL korzysta z wielowersyjności (MVCC) i czyta widoczną dla niego wersję. Odczyt z FOR UPDATE ma inne zachowanie, które zobaczymy dalej.

6. REPEATABLE READ — zachowaj migawkę transakcji

Reset do 5,0. Zmieniamy tylko poziom izolacji A. Migawka jest ustalana przy pierwszym poleceniu wymagającym migawki, a nie przez samo wpisanie BEGIN.

Sesja A

-- Reset first. A1: the first SELECT establishes the transaction snapshot.
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT pg_backend_pid() AS session_a;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- A2: run after B commits; still 5 in the original snapshot.
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
COMMIT;
-- A3: outside the old transaction; now 4.
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';

Sesja B

-- Run the whole block after A1 and before A2.
BEGIN;
UPDATE rsod5_demo.stock SET quantity = quantity - 1 WHERE warehouse_id = 'A';
COMMIT;

Kolejność: A1 → cały blok B → A2 → COMMIT A → A3. Odczyty A: 5, 5, 4. B zatwierdziła zmianę, ale A2 pozostaje przy starej migawce. Dopiero A3, poza poprzednią transakcją, zobaczy nowy stan.

Powtarzalny odczyt nie oznacza „zablokowano wszystkim możliwość zapisu”. B mogła wykonać i zatwierdzić swoją aktualizację. A czytała wcześniejszą widoczną wersję. Silniejsza izolacja nie jest po prostu większą liczbą blokad.

Poziomy izolacji w PostgreSQL 16

Poziom Brudny odczyt Niepowtarzalny odczyt Fantomy Anomalia serializacji
READ UNCOMMITTED Nie Możliwy Możliwe Możliwa
READ COMMITTED Nie Możliwy Możliwe Możliwa
REPEATABLE READ Nie Nie Nie Możliwa
SERIALIZABLE Nie Nie Nie Nie dla pomyślnie zatwierdzonych transakcji

To tabela dla PostgreSQL, nie uniwersalna tabela wszystkich silników. W PostgreSQL READ UNCOMMITTED działa jak READ COMMITTED, a REPEATABLE READ zapobiega także fantomom, mocniej niż wymaga standard SQL.

Fantom to zmiana zbioru wierszy pasujących do ponownie wykonanego warunku, np. nowy rekord magazynu pojawia się po zatwierdzeniu innej transakcji. Anomalia serializacji oznacza, że wspólnego rezultatu nie da się wyjaśnić żadną kolejnością wykonania całych transakcji jedna po drugiej. Sama stabilna migawka nie chroni każdej reguły obejmującej kilka wierszy.

7. Utracona aktualizacja — odczyt i zapis starej wartości

Dwie osoby chcą niezależnie wydać po jednej sztuce z magazynu A. Każda aplikacja odczytuje 5, oblicza 5 - 1 = 4, a następnie zapisuje 4.

Sesja A

-- Reset first. A1: an application reads 5 and calculates 5 - 1 = 4.
BEGIN;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- A2: run after B1 reads 5. Commit before B2.
UPDATE rsod5_demo.stock SET quantity = 4 WHERE warehouse_id = 'A';
COMMIT;

Sesja B

-- B1: run after A1, before A2. Another application also reads 5.
BEGIN;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- B2: run after A2 commits. Reuse the stale calculated value 4.
UPDATE rsod5_demo.stock SET quantity = 4 WHERE warehouse_id = 'A';
COMMIT;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';

Kolejność: A1 → B1 → A2 → B2. Oba odczyty dały 5; końcowy stan to 4, a powinien wynosić 3. Drugi zapis nadpisał skutek pierwszego tą samą, nieaktualną wartością. To utracona aktualizacja.

Błąd nie wynika z tego, że PostgreSQL pozwolił jednocześnie nadpisywać fizyczny wiersz. Wynika z rozdzielenia odczytu i decyzji oraz ponownego użycia starej wartości w aplikacji. Samo otoczenie tych kroków BEGIN/COMMIT przy READ COMMITTED nie wystarczyło.

8. Bezpieczniejszy zapis: aktualizacja względna z warunkiem

Reset do 5,0. Zamiast zapisywać wyliczoną wcześniej czwórkę, prosimy bazę o odjęcie od aktualnej wartości, o ile towar jest dostępny.

Sesja A

-- Reset first. A1: hold a row lock until COMMIT.
BEGIN;
UPDATE rsod5_demo.stock SET quantity = quantity - 1
WHERE warehouse_id = 'A' AND quantity >= 1
RETURNING quantity;
-- A2: run after B1 starts waiting. This unblocks B.
COMMIT;

Sesja B

-- B1: run after A1. The UPDATE waits for A; the timeout limits accidental hangs.
BEGIN;
SET LOCAL lock_timeout = '60s';
UPDATE rsod5_demo.stock SET quantity = quantity - 1
WHERE warehouse_id = 'A' AND quantity >= 1
RETURNING quantity;
-- B2: run after A2; UPDATE has returned 3. Commit B.
COMMIT;
SELECT * FROM rsod5_demo.stock ORDER BY warehouse_id;

Kolejność: A1 → uruchom B1 → A2 → B1 kończy oczekiwanie → B2. A zwróci 4 i utrzyma blokadę wiersza. B poczeka. Po zatwierdzeniu A baza uwzględni nową wersję wiersza, ponownie sprawdzi warunek i odejmie 1: B zwróci 3. Końcowy stan to 3,0.

RETURNING pokazuje zmienioną wartość. Jeżeli nie ma już sztuki do wydania, UPDATE nie zmieni wiersza i nie zwróci wartości. Aplikacja musi sprawdzić ten przypadek — brak błędu SQL nie oznacza automatycznie udanej rezerwacji. CHECK stanowi dodatkową ochronę, ale nie zastępuje interpretacji wyniku.

9. SELECT FOR UPDATE — zablokuj przed decyzją

Gdy logika wymaga kilku kroków po odczycie, można najpierw zablokować wiersz. Blokadę utrzymujemy do końca transakcji, a nie tylko do końca SELECT.

Sesja A

-- Reset first. A1: lock the row before making an application-side decision.
BEGIN;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A' FOR UPDATE;
-- A2: run while B1 waits. Use the value read while holding the lock.
UPDATE rsod5_demo.stock SET quantity = 4 WHERE warehouse_id = 'A';
COMMIT;

Sesja B

-- B1: run after A1. SELECT waits; after A2 it returns the NEW value 4.
BEGIN;
SET LOCAL lock_timeout = '60s';
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A' FOR UPDATE;
-- B2: calculate 4 - 1 = 3 only after the locking read returns.
UPDATE rsod5_demo.stock SET quantity = 3 WHERE warehouse_id = 'A';
COMMIT;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';

Kolejność: A1 → uruchom B1 → A2 → B1 zwraca 4 → B2. B oblicza 4 - 1, dopiero gdy dostanie aktualną wartość wraz z blokadą. Rezultat to 3. Wszystkie konkurujące ścieżki aplikacji muszą stosować protokół zgodny z regułą danych; pojedyncza poprawna ścieżka nie naprawi innych.

Nie utrzymuj transakcji otwartej podczas rozmowy z użytkownikiem ani oczekiwania na formularz. W prawdziwej aplikacji transakcje powinny być krótkie. Przy blokowaniu kilku wierszy stosuj spójną kolejność, np. rosnący klucz magazynu. Ogranicza to ryzyko zakleszczenia: A czeka na zasób B, a B na zasób A. PostgreSQL wykrywa zakleszczenia i przerywa jedną z transakcji; aplikacja musi obsłużyć błąd i w uzasadnionym przypadku ponowić całą operację.

10. SERIALIZABLE — konflikt może wymagać ponowienia

Najsilniejszy poziom wymaga, aby rezultat pomyślnie zatwierdzonych transakcji był zgodny z pewną kolejnością ich wykonania jedna po drugiej. Nie oznacza to, że silnik fizycznie wykonuje wszystkie transakcje po kolei. Gdy wykryje konflikt uniemożliwiający zachowanie gwarancji, może odrzucić transakcję.

Sesja A

-- Reset first. A1: both transactions must read the same initial snapshot.
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- A2: run after B1, before B2.
UPDATE rsod5_demo.stock SET quantity = quantity - 1 WHERE warehouse_id = 'A';
COMMIT;

Sesja B

-- B1: run after A1, before A2.
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
-- B2: run after A2 commits. Expected serialization failure: SQLSTATE 40001.
UPDATE rsod5_demo.stock SET quantity = quantity - 1 WHERE warehouse_id = 'A';
-- B3: recover, then retry the ENTIRE transaction using a new snapshot.
ROLLBACK;
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';
UPDATE rsod5_demo.stock SET quantity = quantity - 1 WHERE warehouse_id = 'A';
COMMIT;
SELECT quantity FROM rsod5_demo.stock WHERE warehouse_id = 'A';

Kolejność: A1 → B1 → A2 → B2 → B3. B2 otrzymuje błąd serializacji 40001, bo próbuje zmienić wiersz zmieniony od czasu jej migawki. Po ROLLBACK ponawiamy całą transakcję, łącznie z odczytem i decyzją. Nowy odczyt B daje 4, a zakończona aktualizacja pozostawia 3.

W tym konkretnym przykładzie konflikt aktualizacji zostałby odrzucony także przy REPEATABLE READ. Wybieramy SERIALIZABLE, aby pokazać sposób obsługi błędu na poziomie gwarantującym serializowalność; ta demonstracja sama w sobie nie dowodzi różnicy między tymi dwoma poziomami.

W aplikacji ponowienia powinny być ograniczone i zaprojektowane razem z operacjami zewnętrznymi. Wycofanie transakcji bazy nie cofa wysłanego e-maila ani wywołania zewnętrznego API. Nie powtarzamy takich efektów bez kontroli.

11. Który mechanizm wybrać?

Problem Punkt wyjścia Co trzeba jeszcze sprawdzić
Kilka zapisów jednej operacji Jedna jawna transakcja Błędy, liczba zmienionych wierszy, reguły biznesowe
Przypadkowa część operacji SAVEPOINT Co ma pozostać po częściowym cofnięciu?
Proste wydanie jednej sztuki Warunkowe UPDATE quantity = quantity - 1 Brak dopasowania i wynik RETURNING
Kilka decyzji na podstawie wiersza SELECT ... FOR UPDATE Krótka transakcja, kolejność blokowania
Spójny obraz raportu z kilku odczytów REPEATABLE READ Raport dotyczy jednej migawki; nie rezerwuje zasobów
Złożona reguła współbieżna Analiza ograniczeń i SERIALIZABLE Obsługa konfliktów i ponowienie całej transakcji

Silniejszą izolację dobieramy do reguły, której musimy bronić. Jej koszty mogą obejmować zarządzanie wersjami, śledzenie zależności, oczekiwanie lub ponowienia. Nie zakładamy, że każdy wyższy poziom po prostu blokuje całą bazę i zawsze działa wolniej.

12. Podsumowanie kursu — pytania do dyskusji

  1. Dlaczego dwa poprawne UPDATE bez wspólnej transakcji mogą zepsuć przeniesienie?
  2. Czy COMMIT gwarantuje zachowanie sumy towaru, jeśli nie zapisaliśmy tej reguły?
  3. Dlaczego przy READ COMMITTED A widziała 5, potem 5, a potem 4?
  4. Czy REPEATABLE READ uniemożliwił sesji B zatwierdzenie zmiany?
  5. Dlaczego zapis stałej 4 różni się od quantity = quantity - 1?
  6. Dlaczego po błędzie 40001 powtarzamy odczyt, a nie samo ostatnie UPDATE?
  7. Co stanie się z blokadą FOR UPDATE po ROLLBACK?
  1. Między osobnymi zatwierdzeniami może nastąpić błąd lub awaria.
  2. Nie. Potrzebna jest poprawna logika i odpowiednie zabezpieczenia reguły.
  3. Niezatwierdzona zmiana B była niewidoczna; po jej zatwierdzeniu nowe polecenie A zobaczyło nowszy stan.
  4. Nie. A zachowała migawkę, B mogła zapisać i zatwierdzić dane.
  5. Stała pochodziła ze starego odczytu. Względne UPDATE działa na wersji wiersza dostępnej do aktualizacji i ponownie sprawdza warunek po oczekiwaniu.
  6. Decyzja opierała się na starej migawce. Nowa transakcja musi podjąć ją ponownie.
  7. Blokada zostanie zwolniona wraz z zakończeniem transakcji.

Na laboratorium połączymy cały przebieg: model encji i klucze, ograniczenia tabel, operacje SQL, funkcje PL/pgSQL i transakcje. Poprawna baza ma zarówno dobry model danych, jak i poprawnie zaprojektowane operacje na tych danych.

Dokumentacja PostgreSQL 16