Bạn đang mắc kẹt vì không muốn gói gọn mọi thứ trong một truy vấn lớn, bởi vì điều đó cũng sẽ không thực sự giải quyết được bất cứ điều gì, nó chỉ làm cho nó ít xảy ra hơn.
Những gì bạn cần là khóa trên các hàng hoặc khóa trên chỉ mục nơi hàng mới sẽ được chèn.
Vậy làm cách nào để chúng ta có được những chiếc khóa độc quyền?
Hai kết nối, mysql1 và mysql2, mỗi kết nối yêu cầu một khóa độc quyền bằng cách sử dụng SELECT ... FOR UPDATE
. Bảng 'lịch sử' có một cột 'user_id' được lập chỉ mục. (Nó cũng là một khóa ngoại.) Không có hàng nào được tìm thấy, vì vậy cả hai dường như vẫn tiến hành bình thường như thể không có gì bất thường sẽ xảy ra. User_id 2808 hợp lệ nhưng không có gì trong lịch sử.
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);
... và tôi không nhận lại được lời nhắc của mình ... không có phản hồi ... vì một phiên khác cũng bị khóa ... nhưng sau đó:
mysql2> insert into history(user_id) values (2808);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Sau đó, mysql1 ngay lập tức trả về thành công khi chèn.
Query OK, 1 row affected (3.96 sec)
Tất cả những gì còn lại là dành cho mysql1 đến COMMIT
và thật kỳ diệu, chúng tôi đã ngăn người dùng có 0 mục nhập chèn nhiều hơn 1 mục nhập. Bế tắc xảy ra vì cả hai phiên đều cần những thứ không tương thích xảy ra:mysql1 cần mysql2 giải phóng khóa của nó trước khi nó có thể cam kết và mysql2 cần mysql1 giải phóng khóa của nó trước khi có thể chèn. Ai đó phải thua cuộc chiến đó, và nói chung chuỗi nào làm được ít công việc nhất là người thua cuộc.
Nhưng điều gì sẽ xảy ra nếu đã có 1 hoặc nhiều hàng tồn tại khi tôi thực hiện SELECT ... FOR UPDATE
? Trong trường hợp đó, khóa sẽ nằm trên các hàng, vì vậy phiên thứ hai hãy thử SELECT
thực sự sẽ chặn khi chờ SELECT
cho đến khi phiên đầu tiên quyết định COMMIT
hoặc ROLLBACK
, tại thời điểm đó phiên thứ hai sẽ có số lượng hàng chính xác (bao gồm bất kỳ hàng nào được chèn hoặc xóa bởi phiên đầu tiên) và có thể quyết định chính xác người dùng đã có số hàng tối đa được phép hay chưa.
Bạn không thể vượt qua điều kiện của cuộc đua, nhưng bạn có thể khóa chúng lại.