Sei fuori dai guai per non voler incapsulare tutto in una query di grandi dimensioni, perché nemmeno questo risolverà nulla, lo rende solo meno probabile.
Ciò di cui hai bisogno sono i blocchi sulle righe o i blocchi sull'indice in cui verrebbe inserita la nuova riga.
Quindi, come otteniamo lucchetti esclusivi?
Due connessioni, mysql1 e mysql2, ognuna delle quali richiede un blocco esclusivo utilizzando SELECT ... FOR UPDATE
. La tabella 'storia' ha una colonna 'user_id' che è indicizzata. (È anche una chiave esterna.) Non sono state trovate righe, quindi entrambi sembrano procedere normalmente come se non accadesse nulla di insolito. User_id 2808 è valido ma non ha nulla nella cronologia.
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);
... e non ricevo il mio prompt ... nessuna risposta ... perché anche un'altra sessione ha un blocco ... ma poi:
mysql2> insert into history(user_id) values (2808);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Quindi mysql1 restituisce immediatamente il successo sull'inserto.
Query OK, 1 row affected (3.96 sec)
Tutto ciò che resta è per mysql1 su COMMIT
e magicamente, abbiamo impedito a un utente con 0 voci di inserire più di 1 voce. Il deadlock si è verificato perché entrambe le sessioni avevano bisogno di cose incompatibili:mysql1 aveva bisogno di mysql2 per rilasciare il suo blocco prima che potesse essere in grado di eseguire il commit e mysql2 aveva bisogno di mysql1 per rilasciare il suo blocco prima che potesse essere inserito. Qualcuno deve perdere quella battaglia, e generalmente il filo che ha fatto meno lavoro è il perdente.
Ma cosa succede se c'erano già 1 o più righe esistenti quando ho eseguito il SELECT ... FOR UPDATE
? In tal caso, il blocco sarebbe stato sulle righe, quindi la seconda sessione per provare a SELECT
bloccherebbe effettivamente l'attesa di SELECT
fino a quando la prima sessione non ha deciso di COMMIT
o ROLLBACK
, a quel punto la seconda sessione avrebbe visto un conteggio accurato del numero di righe (incluse quelle inserite o eliminate dalla prima sessione) e avrebbe potuto decidere con precisione che l'utente aveva già il massimo consentito.
Non puoi superare una race condition, ma puoi escluderli.