Lekcje modułu (3/4)
Blokowanie i SELECT FOR UPDATE
Zrozumienie, jak baza danych działa w izolacji na jednym terminalu, jest proste… nie, czekaj, już to mówiłem!
Czasami „Aktualizacje atomowe” (np. stock = stock - 1) nie wystarczą.
Wyobraźmy sobie, że musisz sprawdzić, czy użytkownik ma wystarczające saldo, aby kupić rzeczy o wartości 50 €:
- KODEF0
- JS: Jeśli saldo wynosi < 50, zablokuj ich!
- W przeciwnym razie przejdź do kasy...
- Uruchom odliczenie! (A tutaj znajdziesz użytkownika, który podczas sprawdzania czeku w kroku 2 wystrzelił inny równoległy, który spalił jego konto i teraz spadasz poniżej zera!).
DLA AKTUALIZACJI Wyraźne blokowanie
Dodanie FOR UPDATE na końcu SELECT magicznie przekształca twój niewinny odczyt w zapieczętowaną blokadę przeciwko innym klientom aż do następnego COMMIT. Nikt inny nie będzie mógł wykonać AKTUALIZACJI (lub równoczesnego odczytu w celu aktualizacji) w tych konkretnych wybranych wierszach, dopóki nie zdecydujesz, co z nimi zrobić!
BEGIN;
-- If id=3 had already been locked by T1, T2 will sit in perpetual loading on this "read"
-- frozen in place, before receiving the raw data, until T1 runs COMMIT freeing everyone!
SELECT balance
FROM users
WHERE id = 3
FOR UPDATE;
-- at this point our Node JS instance lives with the guarantee, knowing for certain that NO ONE ELSE in the world has
-- manipulated (nor been able to read with intent to alter) that balance while we complete the application logic
UPDATE users SET balance = balance - 50 WHERE id = 3;
COMMIT;Istnieje nawet FOR SHARE, który pozwala innym czytelnikom kontynuować czytanie (jeśli są na blokadzie podstawowej) bez blokowania odczytów stanu, ale blokowania wszelkich możliwych kolejnych aktualizacji, dopóki nie zatwierdzisz. Jednak w przypadku modyfikacji zawsze używaj brutalnej siły wyłącznej klauzuli blokującej:
Spróbuj sam
Otwórz normalną transakcję ograniczoną w czasie i zablokuj wyłącznie odczyt identyfikatora produktu=5. Uruchom zapytanie dwuetapowe: 1. Otwórz transakcję (BEGIN) 2. Zapytanie „produkty” pobierze pełny wiersz dla identyfikatora 5 z gwarancją, że nikt go nie zmieni, poprzez dołączenie opcji „DO AKTUALIZACJI”.
Pokaż wskazówkę
BEGIN;, nowa linia, WYBIERZ * Z produktów GDZIE id = 5 DLA AKTUALIZACJI;
Rozwiązanie dostępne po 3 próbach
Czasami transakcja zawiesza się na katastrofalne okresy (zakleszczenie lub nieskończone oczekiwanie). Aby temu zapobiec, możesz nakazać bazie danych, aby zrezygnowała w momencie, gdy wiersz nie jest od razu dostępny: używając na końcu NOWAIT, co spowodowałoby powrót wyjątku serwera SQL do JS w celu jego przechwycenia bez zawieszania się na zawsze. Uruchom polecenie SELECT dla kategorii ID=1 i użyj funkcji FOR UPDATE, ale wymuszając NOWAIT.
Pokaż wskazówkę
WYBIERZ * Z kategorii GDZIE id = 1 DLA AKTUALIZACJI NOWIT;
Rozwiązanie dostępne po 3 próbach