基于数据库mysql实现悲观锁 乐观锁

2,165 阅读3分钟

背景

悲观锁 乐观锁,本身只是一个概念,有不同的方法可以实现,和数据库本身没有关系。只不过基于数据库实现的悲观锁,用到了数据库排它锁。

当然,也可以延伸到其他地方。他只是一个概念。

这里主要讲,基于数据库,实现悲观锁 乐观锁。

当然,基于其他方法,也可以实现,jdk并发包里的AQS里的CAS,也是乐观锁。

本质上,不管基于哪种方法实现乐观锁,本质都是检验数据。


具体如何校验数据?步骤:
1.从数据源,读旧数据
2.修改数据

修改数据的时候,需要校验数据。1.旧数据和数据源里的数据相等,表示没有被其他线程修改,修改成功;2.不相等,表示已经被其他线程修改,表示数据已经过去,修改失败。

悲观锁

悲观锁,其实就是正常的锁。就是以下几个步骤。

步骤
1.获取锁
2.修改数据
3.释放锁


应用场景
比如,更新数据库里的某行数据。


如何加锁?
查询sql后面加for update。


代码

//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)

//1.查询出商品信息
select status from t_goods where id=1 for update; //获取锁
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1); //修改数据
//3.修改商品status为2
update t_goods set status=2;

//4.提交事务
commit;/commit work; //释放锁

排他锁和共享锁
这里的锁,是排他锁。与排它锁对应的是共享锁,即可以读,但是不能写。排它锁,既不能写,也不能读。

所以,既是悲观锁,又是排它锁。锁的名字,只是一个概念而已。重要的是,要了解他的本质。


行锁和表锁
顾名思义,就是锁一行数据,和锁所有行数据,即整张表数据。

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

乐观锁

乐观锁,不是锁。因为1.没有获取锁和释放锁2.也没有用到锁。

他只是一个概念。

就是实际情况是,1.并发篡改数据的可能性比较小,哪怕是并发高的情况下2.所以,在几步操作里的最后一步修改数据的操作,才校验数据是否冲突。


最佳实践
基于数据库字段的唯一性,我们称为基于数据库版本号。


代码

1.查询出商品信息
select (status,status,version) from t_goods where id=#{id} //获取版本号

2.根据商品信息生成订单

3.修改商品status为2
update t_goods 
set status=2,version=version+1 //修改版本号
where id=#{id} and version=#{version} //校验版本号;

步骤解释
1.获取旧版本号
2.修改数据

修改的时候,要校验版本号字段。
具体怎么校验?获取到的旧数据和数据库里的数据一致,表示没有被别的线程修改,那么就加1。如果值不相等,表示已经被别的线程修改,表示数据已经过期。本次修改失败。

最佳实践

从上文的描述可以知道,写多的情况,适合悲观锁。读多,适合乐观锁。

因为写多,意味着如果使用乐观锁,那么可能会出现很多失败。所以,适合悲观锁。

读多,如果使用悲观锁,那么速度就很慢,并发很低,因为大家都在等待获取锁。所以,适合乐观锁。

参考

www.hollischuang.com/archives/93…