欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

悲观锁 & 乐观锁

程序员文章站 2022-06-02 11:45:51
...

悲观锁:总是假设最坏的情况,每次去拿数据的时候认为somebody会修改,所以每次在拿数据的时候都会上锁,传统的关系型数据库里就用到了很多的这种锁机制,如行锁,表锁,读锁和写锁等,都是在操作之前先上锁;Java中Synchronized和ReetranLock等独占锁就是悲观锁实现的。

悲观锁的实现方式:

悲观锁的实现是依赖于数据库提供的锁机制,流程如下:

1、修改记录前,对记录加上排它锁(exclusive locking)

2、如果加锁失败,说明这条数据正在被修改,那么当前查询要等待或者抛出异常,这由开发者决定

3、如果加锁成功,可以对这条数据修改了,事务完成解锁。

4、加锁修改期间,其他事务也想这条记录进行操作时,都要等待或直接抛出异常

在使用mysql innodb引擎实现悲观锁时,必须关闭mysql的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

eg:

-- 0.开始事务
begin; 
-- 1.查询出商品库存信息
select quantity from items where id=1 for update;
-- 2.修改商品库存为2
update items set quantity=2 where id = 1;
-- 3.提交事务
commit;

使用select...for update会把数据锁住,MySQL InnoDB默认行级锁,行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住。

乐观锁:总是假设最好的situation,每次去拿数据时都believe别人不会修改,所以不会上锁,但是更新的时候会判断一下在此期间别人有没有去更新了这个数据(用版本号和CAS算法实现);乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write-condition机制,其实都是提供的乐观锁。

乐观锁的实现方式

乐观锁的实现不需要借助于数据库锁机制,只需要两个步骤,冲突检测和数据更新,其中一种典型的实现方法就是CAS(Compare and Swap)

CAS实现乐观锁

CAS是一种乐观锁实现方式,顾名思义就是先比较后更新。在对一个数据进行更新前,先持有这个数据原有值的备份。比如要将a=2更新为a=3,在进行更新前会比较此刻a是否为2,如果是2,才会进行更新操作,当多个线程尝试使用CAS同时更新一个变量时,只有一个线程能够成功,其余都是失败,失败的线程不会被挂起,而是被告知这次竞争失败,并且可以再次尝试。

-- 查询出商品库存信息,quantity = 3
select quantity from items where id=1
-- 修改商品库存为2
update items set quantity=2 where id=1 and quantity = 3;

在更新之前,先查询库存表中当前库存数,然后在做update时,以库存数作为一个修改条件。当进行提交更新的时候,判断数据库的当前库存数与第一次取出来的库存数进行比对,相等则更新,否则认为是过期数据,但是这种更新存在一个比较严重的问题,即ABA问题。

ABA问题

A线程取出库存数3,B线程取出库存数3,B线程先将库存数变为2,又将库存数变为3,A线程在进行更新操作时发现库存仍然是3,然后操作成功。尽管A线程操作时成功的,但是不能代表这个过程就是没问题的。

解决ABA问题的一个方法是通过一个顺序递增的version字段:

-- 查询出商品信息,version = 1
select version from items where id=1
-- 修改商品库存为2
update items set quantity=2,version = 2 where id=1 and version = 1;

在每次执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据版本号一致就可以执行修改操作并对版本号执行+1操作,否则执行失败,因为每次修改操作都会将版本号增加,所以不会出现ABA问题,还可以使用时间戳,因为时间戳自然具有顺序递增性。

乐观锁和悲观锁

乐观锁并不是真正的加锁,优点是效率高,缺点是更新失败的概率比较高;悲观锁依赖于数据库锁机制,更新失败的概率比较低,但是效率也低。