Anda lolos karena tidak ingin merangkum semuanya dalam satu kueri besar, karena itu juga tidak akan menyelesaikan apa pun, hanya membuatnya lebih kecil kemungkinannya.
Yang Anda butuhkan adalah kunci pada baris, atau kunci pada indeks tempat baris baru akan disisipkan.
Jadi bagaimana kita mendapatkan kunci eksklusif?
Dua koneksi, mysql1 dan mysql2, masing-masing meminta kunci eksklusif menggunakan SELECT ... FOR UPDATE
. Tabel 'riwayat' memiliki kolom 'user_id' yang diindeks. (Ini juga merupakan kunci asing.) Tidak ada baris yang ditemukan, jadi keduanya tampak berjalan normal seolah-olah tidak akan terjadi sesuatu yang tidak biasa. User_id 2808 valid tetapi tidak memiliki riwayat apa pun.
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);
... dan saya tidak mendapatkan prompt saya kembali ... tidak ada tanggapan ... karena sesi lain juga memiliki kunci ... tapi kemudian:
mysql2> insert into history(user_id) values (2808);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Kemudian mysql1 segera mengembalikan sukses pada penyisipan.
Query OK, 1 row affected (3.96 sec)
Yang tersisa adalah untuk mysql1 ke COMMIT
dan secara ajaib, kami mencegah pengguna dengan 0 entri memasukkan lebih dari 1 entri. Kebuntuan terjadi karena kedua sesi membutuhkan hal-hal yang tidak kompatibel untuk terjadi:mysql1 membutuhkan mysql2 untuk melepaskan kuncinya sebelum dapat melakukan dan mysql2 membutuhkan mysql1 untuk melepaskan kuncinya sebelum dapat dimasukkan. Seseorang harus kalah dalam pertarungan itu, dan umumnya pihak yang paling sedikit berhasil adalah yang kalah.
Tetapi bagaimana jika sudah ada 1 atau lebih baris ketika saya melakukan SELECT ... FOR UPDATE
? Dalam hal ini, kunci akan berada di baris, jadi sesi kedua mencoba SELECT
akan benar-benar memblokir menunggu SELECT
sampai sesi pertama memutuskan untuk COMMIT
atau ROLLBACK
, pada saat sesi kedua akan melihat penghitungan jumlah baris yang akurat (termasuk semua yang dimasukkan atau dihapus oleh sesi pertama) dan dapat secara akurat memutuskan pengguna sudah memiliki jumlah maksimum yang diizinkan.
Anda tidak dapat melampaui kondisi balapan, tetapi Anda dapat menguncinya.