Blokada

Mechanizm tymczasowo ograniczający dostęp do wiersza lub tabeli, by zapobiec konfliktom przy równoczesnych operacjach. Może być współdzielona lub wyłączna.

Blokada (ang. lock) to mechanizm bazy danych, który na chwilę ogranicza dostęp do wiersza, strony lub całej tabeli, żeby dwie operacje działające równocześnie nie podeptały sobie nawzajem danych. Mówiąc po ludzku: kiedy jedna transakcja modyfikuje rekord, baza zakłada na niego blokadę, a reszta musi grzecznie poczekać w kolejce, aż tamta skończy.

Blokady dzielą się głównie na dwa rodzaje. Blokada współdzielona (shared lock, S) pozwala wielu transakcjom czytać ten sam wiersz jednocześnie, ale nikt nie może go w tym czasie zmienić. Blokada wyłączna (exclusive lock, X) zakładana jest przy zapisie (UPDATE, DELETE, INSERT) i blokuje wszystkich pozostałych — i czytających, i piszących — dopóki transakcja nie zrobi COMMIT albo ROLLBACK.

Po co to w ogóle jest

Bez blokad straciłbyś spójność danych. Wyobraź sobie dwóch klientów kupujących ostatni bilet w tej samej milisekundzie — bez blokowania oboje dostaliby potwierdzenie, a sala ma jedno miejsce. Blokady wymuszają, żeby takie operacje szły jedna po drugiej. To fundament tzw. izolacji transakcji, czyli litery I w akronimie ACID.

Im mniejszy zakres blokady, tym lepiej dla wydajności. Blokada na poziomie wiersza (row-level lock) wstrzymuje tylko jeden rekord i pozwala innym pracować na reszcie tabeli. Blokada na poziomie całej tabeli jest prostsza, ale potrafi zakorkować cały system.

Przykład z praktyki

W PostgreSQL czy MySQL (silnik InnoDB) jawną blokadę wiersza zakładasz tak:

SELECT * FROM zamowienia WHERE id = 42 FOR UPDATE;

Od tego momentu, aż do końca transakcji, nikt inny nie zmodyfikuje wiersza o id = 42 — możesz spokojnie odczytać stan magazynu, zmniejszyć go i zapisać, mając pewność, że nikt po drodze nie podmienił liczby. Konkurencyjny UPDATE tego samego wiersza będzie czekał, aż zrobisz COMMIT.

Na co uważać

Najgroźniejszy jest deadlock: transakcja A trzyma wiersz 1 i chce wiersz 2, a transakcja B trzyma wiersz 2 i chce wiersz 1. Obie czekają w nieskończoność. Bazy zwykle to wykrywają i ubijają jedną z transakcji z błędem — wtedy aplikacja musi ją ponowić. Najprostsza profilaktyka: zawsze blokuj zasoby w tej samej kolejności.

Drugi częsty mit to przekonanie, że „blokada równa się problem z wydajnością”. Sama w sobie nie — problemem są zbyt długie transakcje, które trzymają blokady minutami, oraz blokowanie zbyt szerokiego zakresu danych. Trzymaj transakcje krótkie i nie rób w ich środku wywołań do zewnętrznych API.

Pojęcia powiązane: transakcja, ACID, poziomy izolacji (isolation levels), deadlock, MVCC (wersjonowanie wierszy, które w PostgreSQL ogranicza potrzebę blokad przy odczycie), optimistic vs pessimistic locking.