Istnieją dwa ogólne podejścia do blokowania.
Po pierwsze, masz pesymistyczne blokowanie. W tym podejściu blokujesz wiersz (SELECT ... FOR UPDATE
), co uniemożliwia innym osobom zmianę wiersza. Następnie wykonujesz UPDATE
. Po zatwierdzeniu zmiany blokada zostaje zwolniona. W tym przypadku nie ma potrzeby posiadania kolumny z numerem wersji/znacznikiem czasu (przynajmniej po to, aby nie obsługiwać blokowania), a kod jest stosunkowo prosty.
Wadą pesymistycznego blokowania jest to, że musisz trzymać blokadę przez cały czas, gdy użytkownik siedzi na stronie potencjalnie edytującej dane. Jest to technicznie bardzo trudne, jeśli tworzysz aplikację internetową, ponieważ HTTP jest protokołem bezstanowym. Żądanie, które początkowo renderuje stronę, normalnie otrzymałoby połączenie z puli połączeń, wykonaj SELECT
, a następnie zwróć połączenie do puli po zakończeniu strony. Kolejne żądanie aktualizacji danych zwykle odbywałoby się przy innym połączeniu z inną sesją bazy danych, więc nie można zablokować wiersza w pierwszej sesji i zaktualizować go w drugiej. Jeśli chcesz pesymistycznie zablokować wiersz, musisz wykonać dużo pracy na zapleczu, aby upewnić się, że jedno połączenie z bazą danych jest powiązane z określoną sesją warstwy środkowej, dopóki użytkownik nie zakończy edycji danych. Ogólnie rzecz biorąc, ma to bardzo negatywny wpływ na skalowalność i wprowadza różnego rodzaju problemy z zarządzaniem sesjami — skąd wiadomo, na przykład, czy zażądałem strony, zablokowałem wiersz, a następnie zamknąłem przeglądarkę bez wylogowania lub wprowadzenia zmian? Jak długo zamierzasz pozostawić rekord zablokowany w bazie danych? Co się stanie, jeśli inna sesja spróbuje zablokować wiersz? Jak długo pozwolisz tej sesji czekać na zamek, jeśli pierwsza osoba wyjdzie na lunch? Ogólnie rzecz biorąc, ludzie nie wdrażają pesymistycznego blokowania w aplikacjach internetowych, ponieważ zarządzanie sesjami i stanem sesji jest po prostu zbyt niepraktyczne.
Drugą opcją jest optymistyczne blokowanie. W tym podejściu dodajesz numer wersji/sygnaturę czasową do wiersza. Ten numer wersji/sygnaturę czasową wybiera się podczas zapytania o dane. Następnie użyj tego w swoim WHERE
klauzulę, gdy później wykonasz aktualizację i sprawdzisz, ile wierszy zostało faktycznie zmodyfikowanych. Jeśli zmodyfikujesz dokładnie jeden wiersz, wiesz, że wiersz nie zmienił się od czasu jego przeczytania. Jeśli zmodyfikujesz 0 wierszy, wiesz, że wiersz się zmienił i możesz poradzić sobie z błędem.
Na przykład wybierz dane wraz z numerem wersji
SELECT address_line1, city, state, zip, version
FROM addressTable
WHERE address_id = `<<some key>>`
Kiedy byłeś gotowy do aktualizacji, zrobiłbyś coś takiego, gdzie używasz version
w Twojej UPDATE
i wyrzuć błąd, jeśli wiersz się zmienił
UPDATE addressTable
SET address_line1 = `<<new address line 1>>`,
city = `<<new city>>`,
state = `<<new state>>`,
zip = `<<new zip>>`,
version = version + 1
WHERE address_id = `<<some key>>`
AND version = `<<version you read initially>>`
IF( SQL%ROWCOUNT = 0 )
THEN
-- Darn. The row must have changed since you read it. Do something to
-- alert the user. Most likely, the application will need to re-query the
-- data to see what the address has been changed to and then ask the user
-- whether they want to re-apply the changes.
RAISE_APPLICATION_ERROR( -20001, 'Oops, the row has changed since you read it.' );
END IF;
Twoja aplikacja zrobiłaby wtedy coś pożytecznego z błędem. Normalnie oznaczałoby to wykonanie czegoś takiego jak ponowne zapytanie o dane, przedstawienie zmian użytkownikowi i zapytanie go, czy nadal chce zastosować swoje zmiany. Jeśli np. przeczytam adres i zacznę go edytować, pójdę na lunch, kolega się zaloguje, odczyta ten sam adres, dokona kilku zmian i zapisze, to wracam i próbuję zapisać swoje zmiany, to generalnie miałoby to sens aby pokazać mi coś, co mówi mi, że mój kolega zmienił już adres na coś nowego — czy chcę kontynuować wprowadzanie zmian, czy też chcę je porzucić.