MySQL中的锁

共享锁和排它锁

这两种锁都是悲观锁(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写锁会导致死锁!这种场景,其实无非两种办法:

  1. insert 报错:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ‘xxxxxxxxx’ for key ‘cellphone’,这时,通过代码可以得到sql的报错代码是23000,如果发现是这个错误代码,那直接告诉api,提示手机号已注册即可。

  2. 使用防抖功能,因为很有可能是由于用户手抖点击了两次按钮照成的。

  3. 使用 redis的 setnx 指令。

发表评论

电子邮件地址不会被公开。 必填项已用*标注