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
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. |
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:
- Zakończ wcześniejsze transakcje w obu sesjach (
COMMITalboROLLBACK). - W jednej sesji uruchom
99_reset.sql: wracamy do A=5, B=0. - 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
- Dlaczego dwa poprawne
UPDATEbez wspólnej transakcji mogą zepsuć przeniesienie? - Czy
COMMITgwarantuje zachowanie sumy towaru, jeśli nie zapisaliśmy tej reguły? - Dlaczego przy
READ COMMITTEDA widziała 5, potem 5, a potem 4? - Czy
REPEATABLE READuniemożliwił sesji B zatwierdzenie zmiany? - Dlaczego zapis stałej
4różni się odquantity = quantity - 1? - Dlaczego po błędzie
40001powtarzamy odczyt, a nie samo ostatnieUPDATE? - Co stanie się z blokadą
FOR UPDATEpoROLLBACK?
- Między osobnymi zatwierdzeniami może nastąpić błąd lub awaria.
- Nie. Potrzebna jest poprawna logika i odpowiednie zabezpieczenia reguły.
- Niezatwierdzona zmiana B była niewidoczna; po jej zatwierdzeniu nowe polecenie A zobaczyło nowszy stan.
- Nie. A zachowała migawkę, B mogła zapisać i zatwierdzić dane.
- Stała pochodziła ze starego odczytu. Względne
UPDATEdziała na wersji wiersza dostępnej do aktualizacji i ponownie sprawdza warunek po oczekiwaniu. - Decyzja opierała się na starej migawce. Nowa transakcja musi podjąć ją ponownie.
- 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.