Nie masz ochoty zamykać wszystkiego w jednym dużym zapytaniu, ponieważ to też niczego nie rozwiąże, po prostu zmniejsza prawdopodobieństwo.
Potrzebujesz blokad na wierszach lub blokad na indeksie, w którym zostanie wstawiony nowy wiersz.
Jak więc uzyskać ekskluzywne zamki?
Dwa połączenia, mysql1 i mysql2, każde z nich żąda blokady na wyłączność za pomocą SELECT ... FOR UPDATE
. Tabela 'historia' zawiera kolumnę 'user_id', która jest indeksowana. (Jest to również klucz obcy). Nie znaleziono żadnych wierszy, więc wydaje się, że oba działają normalnie, tak jakby nie wydarzyło się nic niezwykłego. Identyfikator użytkownika 2808 jest prawidłowy, ale nie ma nic w historii.
mysql1> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql2> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql1> select * from history where user_id = 2808 for update;
Empty set (0.00 sec)
mysql2> select * from history where user_id = 2808 for update;
Empty set (0.00 sec)
mysql1> insert into history(user_id) values (2808);
... i nie otrzymuję monitu ... brak odpowiedzi ... ponieważ inna sesja też ma blokadę ... ale potem:
mysql2> insert into history(user_id) values (2808);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Następnie mysql1 natychmiast zwraca sukces wstawiania.
Query OK, 1 row affected (3.96 sec)
Pozostało tylko mysql1 COMMIT
i magicznie uniemożliwiliśmy użytkownikowi z 0 wpisami wstawienie więcej niż 1 wpisu. Zakleszczenie nastąpiło, ponieważ obie sesje wymagały niekompatybilnych rzeczy:mysql1 potrzebował mysql2 do zwolnienia blokady przed zatwierdzeniem, a mysql2 potrzebował mysql1 do zwolnienia blokady przed włożeniem. Ktoś musi przegrać tę walkę, a generalnie wątek, który wykonał najmniej pracy, to przegrany.
Ale co by było, gdyby istniał 1 lub więcej wierszy, kiedy wykonałem SELECT ... FOR UPDATE
? W takim przypadku blokada byłaby na wierszach, więc druga sesja, aby spróbować SELECT
faktycznie zablokowałoby oczekiwanie na SELECT
dopóki pierwsza sesja nie zdecydowała się na COMMIT
lub ROLLBACK
, w którym to momencie druga sesja wyświetliłaby dokładną liczbę wierszy (w tym wszystkie wstawione lub usunięte przez pierwszą sesję) i mogłaby dokładnie zdecydować, że użytkownik ma już maksymalną dozwoloną liczbę.
Nie możesz pokonać warunków wyścigu, ale możesz je zablokować.