Не сте обвързани с това, че не искате да капсулирате всичко в една голяма заявка, защото това всъщност също няма да реши нищо, а само прави по-малко вероятно.
Това, от което се нуждаете, са ключалки на редовете или ключалки на индекса, където ще бъде вмъкнат новият ред.
И така, как да получим изключителни ключалки?
Две връзки, mysql1 и mysql2, всяка от които изисква ексклузивно заключване с помощта на SELECT ... FOR UPDATE
. Таблицата 'history' има колона 'user_id', която е индексирана. (Това също е външен ключ.) Няма открити редове, така че изглежда, че и двата работят нормално, сякаш нищо необичайно няма да се случи. User_id 2808 е валиден, но няма нищо в историята.
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);
... и не получавам подканата си обратно ... няма отговор ... защото друга сесия също има заключване ... но след това:
mysql2> insert into history(user_id) values (2808);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Тогава mysql1 незабавно връща успех на вмъкването.
Query OK, 1 row affected (3.96 sec)
Всичко, което остава, е mysql1 да COMMIT
и магически предотвратихме потребител с 0 записа да вмъкне повече от 1 запис. Безизходицата възникна, защото и двете сесии се нуждаеха от несъвместими неща, за да се случат:mysql1 се нуждаеше от mysql2, за да освободи заключването си, преди да може да извърши ангажимент, а mysql2 имаше нужда от mysql1, за да освободи заключването си, преди да може да вмъкне. Някой трябва да загуби тази битка и като цяло нишката, която е свършила най-малко работа, е губеща.
Но какво ще стане, ако вече имаше 1 или повече реда, когато направих SELECT ... FOR UPDATE
? В този случай заключването щеше да е на редовете, така че втората сесия да се опита да SELECT
всъщност би блокирал чакането на SELECT
докато първата сесия реши или да COMMIT
или ROLLBACK
, в който момент втората сесия щеше да види точно преброяване на броя на редовете (включително всички вмъкнати или изтрити от първата сесия) и можеше точно да реши, че потребителят вече има максимално разрешения.
Не можете да надминете условията на състезанието, но можете да ги заключите.