背景
悲观锁 乐观锁,本身只是一个概念,有不同的方法可以实现,和数据库本身没有关系。只不过基于数据库实现的悲观锁,用到了数据库排它锁。
当然,也可以延伸到其他地方。他只是一个概念。
这里主要讲,基于数据库,实现悲观锁 乐观锁。
当然,基于其他方法,也可以实现,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。如果值不相等,表示已经被别的线程修改,表示数据已经过期。本次修改失败。
最佳实践
从上文的描述可以知道,写多的情况,适合悲观锁。读多,适合乐观锁。
因为写多,意味着如果使用乐观锁,那么可能会出现很多失败。所以,适合悲观锁。
读多,如果使用悲观锁,那么速度就很慢,并发很低,因为大家都在等待获取锁。所以,适合乐观锁。