悲观锁和乐观锁
目录
在并发编程里我们常常会接触到各种锁,因为有锁的存在给我们编程带来了很大方便,比如加锁机制。同时也带来了一些麻烦,比如:死锁
悲观锁
1.介绍
对数据被外界(包括本系统当前的其它事务,以及来自外部系统的事务处理)修改持保守态度.因此,在整个数据处理的过程中,将数据处于锁定状态.
2.实现
依靠数据库提供的锁机制(也只有数据库提供的锁机制才能真正保证数据访问的排他性.否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
3.使用场景举例
商品goods表中有一个字段status. status为1代表商品未被下单,status为2代表商品已经被下单.那么我们对某个商品下单时必须确保该商品status为1. 假设商品的id为1.
如果不加锁,那么操作方法如下:
查询出商品信息
select status from t_goods where id=1;
根据商品信息生成订单
insert into t_orders (id, goolds_id) values (null, 1);
修改商品status为2
update t_goods set status = 2 where id=1;
这种场景在高并发访问的情况下很可能会出现问题!
若我们在第一步查询商品status为1,然后在我们打算update,突然有人提前我们一步对商品进行了update(即status变为了2).注意了哈我们并不知道此时status已经被修改了,这样的话一件商品会被下单两次.你是卖家的话你会把商品卖谁,卖谁都不行.所以说这种方式是不安全的.
那么我们该怎么避免这种情况的发生呢?
使用悲观锁来实现: 当我们在查询出goods信息后就把当前的数据锁定,直到我们update后在解锁.在此过程中,因为goods被锁定了,就不会出现第三者来突然袭击了.
此时有一点需要注意: MySQL数据库默认使用autocommit模式(即当你执行一个更新后,数据库会立刻将结果进行提交),因此,我们需要关闭数据库的自动提交属性(set autocommit=0).此时,我们可以进行我们的业务了.具体操作如下:
开始事务
start transaction;
查询出商品信息
select status from t_goods where id=1 for update;
根据商品信息生成订单
insert into t_orders (id, goolds_id) values (null, 1);
修改商品status为2
update t_goods set status = 2 where id=1;
提交事务
commit; (因为我们事先关闭了数据库的自动提交,所以需要手动控制事务的提交)
select...for update,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的那条数据就被我们锁定了,其它事务必须等本次事务提交之后才能执行.这样保证了当前数据不会被其它事务修改.
注: 当我们执行select status from t_goods where id=1 for update;后.我们在另外的事务中再次执行select status from t_goods where id=1 for update;则第二个事务会一致等待第一个事务的提交.此时第二个查询处于阻塞状态(如果第一个事务一直不提交,第二个事务会报错).但是若在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不受第一个事务的影响.
注: 查询时主键明确是行锁,主键不明确是表锁.
乐观锁
1.介绍
乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会对数据的冲突与否进行检测.如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做.
2.实现乐观锁的两种方式
a.使用数据版本(version)记录机制实现
为数据增加一个版本标识.当读取数据库表时,将version字段的值和需要的数据一起读出.数据每更新一次,version++.当我们提交数据时,比较表中version的值和第一次取出来的version的值是否一致。一致则成功,否则认为是过期数据。
b.使用时间戳
在表中增加一字段,类型是时间戳类型.在提交的时候检查当前表中的数据的时间戳和自己更新前取到的时间戳进行对比.若一致成功,否则就是版本冲突。