CodeGym /Kursy /SQL SELF /Ograniczenia i potencjalne problemy przy pracy z transakc...

Ograniczenia i potencjalne problemy przy pracy z transakcjami: LOCK, DEADLOCK

SQL SELF
Poziom 54 , Lekcja 1
Dostępny

Transakcje w PostgreSQL zapewniają izolację — jedną z kluczowych cech ACID. Do tego system używa blokad. Blokada to mechanizm, który gwarantuje, że kilka transakcji nie będzie się "bić" o ten sam rekord albo tabelę. Wyobraź sobie kolejkę w sklepie: przy kasie obsługiwany jest jeden człowiek na raz. Blokady działają podobnie, tylko pilnują dostępu do danych w tabelach.

Podstawowe typy blokad

W PostgreSQL jest kilka rodzajów blokad, niektóre z nich mogłeś już widzieć w EXPLAIN ANALYZE:

  1. ROW EXCLUSIVE (ekskluzywna blokada wiersza) — pojawia się przy zmianie danych w wierszu. To najczęściej używany typ blokady, np. przy INSERT, UPDATE albo DELETE.
  2. SHARE (współdzielona blokada) — używana przy operacjach, które gwarantują, że dane się nie zmienią, póki trwa zapytanie, np. przy SELECT ... FOR SHARE.
  3. EXCLUSIVE (ekskluzywna blokada) — blokuje wszystkie inne operacje na danych poza odczytem.

Można powiedzieć, że blokady to takie psy stróżujące, które pilnują naszych danych.

Problem blokad: co może pójść nie tak?

Teraz, gdy już wiemy, czym są blokady, zobaczmy, jakie mogą sprawiać problemy. Największy z nich to problem deadlocków. Deadlock (wzajemna blokada) to sytuacja, w której dwie (albo więcej) transakcje czekają na siebie nawzajem, przez co wykonanie utknęło w nieskończonej pętli. Typowo wygląda to tak:

  1. Transakcja 1 blokuje wiersz A i chce dostać się do wiersza B.
  2. Transakcja 2 blokuje wiersz B i chce dostać się do wiersza A.
  3. Ponieważ oba wiersze są zablokowane, żadna z transakcji nie może się zakończyć.

Przykład deadlocka

Żeby poczuć ból programisty, który trafił na deadlocka, wyobraź sobie taki scenariusz:

-- Transakcja 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Jednocześnie
-- Transakcja 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

-- Transakcja 1 próbuje zablokować id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- Transakcja 2 próbuje zablokować id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

Voila! Każda transakcja jest zablokowana przez drugą. PostgreSQL prędzej czy później to wykryje i rzuci błąd deadlocka.

Jak unikać deadlocków?

  1. Stała kolejność dostępu do danych. Staraj się zawsze odwoływać do danych w tej samej kolejności. Jeśli transakcja 1 najpierw blokuje wiersz A, potem wiersz B, to transakcja 2 powinna robić to samo: najpierw wiersz A, potem wiersz B.
-- Prawidłowa kolejność dostępu
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
  1. Minimalizuj czas trwania transakcji. Długie transakcje zwiększają szansę na deadlocka. Kończ transakcje jak najszybciej: BEGIN, zrób wszystkie potrzebne zmiany i od razu COMMIT.

  2. Używaj poziomów izolacji transakcji. Jeśli Twoje zadanie nie wymaga super ścisłej izolacji danych, rozważ użycie poziomu izolacji READ COMMITTED. Pozwala to uniknąć niektórych blokad.

Timeouty i diagnostyka blokad

Są narzędzia do diagnozowania i zapobiegania blokadom w PostgreSQL. Na przykład możesz ustawić maksymalny czas oczekiwania na blokadę:

SET lock_timeout = '5s';  -- Ustawiamy timeout blokady na 5 sekund

Jeśli blokada trwa dłużej niż podany czas, transakcja zostanie przerwana, co zapobiega totalnemu zacięciu.

Śledzenie blokad w PostgreSQL

Jedno z przydatnych poleceń do monitorowania blokad to pg_locks. Pokazuje wszystkie aktywne blokady w systemie:

SELECT * FROM pg_locks;

Możesz zobaczyć, które transakcje trzymają blokady, a które czekają na ich zwolnienie. Szczególnie przydatne przy debugowaniu deadlocków.

Specyfika LOCK i ręczne blokowanie obiektów

Jeśli musisz ręcznie zarządzać blokadami, użyj polecenia LOCK:

LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;

Uwaga: ACCESS EXCLUSIVE to najmocniejsza blokada, blokuje wszystkie inne operacje na tabeli, nawet SELECT. Używaj jej tylko w wyjątkowych przypadkach (np. zmiana struktury tabeli).

Do blokowania pojedynczych wierszy przy zmianach użyj SELECT ... FOR UPDATE:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

To gwarantuje, że inne transakcje nie zmienią tych wierszy do końca Twojej transakcji.

Co warto pamiętać o blokadach?

Blokady to nie zło, tylko narzędzie. Pomagają dbać o integralność danych, ale trzeba z nimi uważać. Oto kilka kluczowych porad:

  1. Nie zaczynaj transakcji, jeśli nie wiesz dokładnie, po co Ci ona.
  2. Kończ transakcję jak najszybciej.
  3. Unikaj dostępu do danych w różnej kolejności.
  4. Używaj poziomów izolacji odpowiednich do Twojego zadania.

Teraz jesteś gotowy na pracę z blokadami i deadlockami! Niech pg_locks i mądrość ACID będą z Tobą.

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