起因:
有这么一个需求:数据库有一张表list
其中有三个字段: id phone state
需要查询出 一条state=0的数据 取出phone 插入到另外一张表 然后设置state=1 代码如下:
//取出符合条件的数据
SELECT * FROM LIST WHERE state = 0 LIMIT 1;
//插入到phone表中
INSERT INTO phone VALUES(NULL,'139490000001');
//设置state状态为1
UPDATE LIST SET state = 1 WHERE phone='139490000001';
上面这种场景单线程访问时正常的,但是在高并发访问的情况下很可能会出现问题。前面已经提到,只有当status为0时才获取这一条数据,上面第一步操作中,查询出来的数据status为0。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对这条数据把 status修改为1了,但是我们并不知道数据已经被修改了,这样就可能造成同一条数据被修改2次,使得数据不一致。所以说这种方式是不安全的。
使用悲观锁来实现
使用悲观锁的原理就是,当我们在查询出信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为list被锁定了,就不会出现有第三者来对其进行修改了。要使用悲观锁,我们必须关闭mysql数据库的自动提交属性。
//设置自动提交事务
set autocommit = 0;
//0.开始事务
//start transaction;
//1.查询出信息
SELECT * FROM LIST WHERE state = 0 LIMIT 1 FOR UPDATE;
//2.插入到phone表中
INSERT INTO phone VALUES(NULL,'139490000001');
//3.设置state状态为1
UPDATE LIST SET state = 1 WHERE phone='139490000001';
//4.提交事务
commit;
需要注意的:
需要注意的是,在事务中,只有SELECT ... FOR UPDATE的查询语句会被阻塞,一般SELECT ... 则不受此影响。
上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键或索引,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
网友评论