共享锁和排它锁
这两种锁都是悲观锁(pessimistic lock)
定义表如下:
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`content` varchar(255) DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ix_content` (`content`(191))
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
读锁(共享锁):允许其他线程上读锁,但是不允许上写锁:
SELECT * FROM test t WHERE t.content = ? LOCK IN SHARE MODE
在事务A中,虽然不打算修改select的行,但是为了保证事务A的数据一致性,事务A也不希望别的事务修改select的行,那就可以上共享锁。
写锁(排他锁):不允许其他线程上任何锁:
SELECT * FROM test t WHERE t.content = ? FOR UPDATE
当事务A执行了上述语句时,事务B也执行相同语句,则会阻塞,直到事务A提交或者回滚。举一个应用场景,请求a和请求b,都想注册同一个手机号,但是实际上系统只允许一个手机号注册成功,一般的做法是
select * from user where cellphone = '13180808080' limit 1 for update
,如果没有,就执行insert操作。另一个请求恰好也执行了相同的查询,它就要等到第一个请求提交,最后发现,手机号已经存在,由业务代码提示报错。
更新
手机号这个问题,似乎并没有解决,看如下SQL顺序:
序号 | session1 | session2 | 描述 |
---|---|---|---|
1 | begin; | begin; | ok; |
2 | select * from user where cellphone="13100009999" limit 1 for update; | Empty set (0.01 sec) | |
3 | select * from user where cellphone="13100009999" limit 1 for update; | Empty set (0.00 sec) | |
4 | insert into user(cellphone) values(13100009999); | waiting… | |
5 | insert into user(cellphone) values(13100009999); | ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction;
同时,序号4返回:Query OK, 1 row affected (10.46 sec) |
可以发现,这种场景下,使用for udpate
写锁会导致死锁!这种场景,其实无非两种办法:
-
insert 报错:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ‘xxxxxxxxx’ for key ‘cellphone’,这时,通过代码可以得到sql的报错代码是23000,如果发现是这个错误代码,那直接告诉api,提示手机号已注册即可。
-
使用防抖功能,因为很有可能是由于用户手抖点击了两次按钮照成的。
-
使用 redis的
setnx
指令。