乐观锁与悲观锁

艺术来源于生活,很多技术解决思路也源于生活
 

  • 现实生活中的锁
  • 什么是乐观锁和悲观锁?
  • 乐观锁,悲观锁的作用
  • 实现乐观锁与悲观锁

 

现实生活中的锁

锁在日常生活中很常见,给盒子上锁,这里锁的作用是锁定盒子里面的资源,只让持有钥匙的人访问,只有一把锁,并且只有一个锁孔,任何人要想看盒子里的东西(锁定的数据),就必须拿到钥匙,这样可以保证每个人按照顺序去打开盒子,替换盒子里的东西(锁定数据)
 

什么是乐观锁和悲观锁?

乐观锁和悲观锁是用来解决并发场景下数据访问安全问题的方案

乐观锁:

乐观锁是抱以乐观的态度来看待数据的安全性,它假设数据一般情况下不会造成冲突,所以会在数据进行提交更新的时候对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量,不会让用户等待

悲观锁:

悲观锁是抱以悲观的态度来看待数据的安全性,它假设数据发生冲突的可能性很大,想操作数据,就必须先获取锁,再进行业务操作,适用于写操作比较多的场景

总结:乐观锁是在数据准备更新时锁住数据,悲观锁是在对数据操作时锁住数据

 

乐观锁,悲观锁的作用

解决并发场景下,让数据安全,准确的访问
 

如何实现乐观锁与悲观锁

乐观锁的实现:乐观锁一般会使用版本号或时间戳机制或CAS算法实现
悲观锁的实现:比如行锁,表锁,读锁,写锁,Synchronized 锁等,都是在做操作之前先上锁

乐观锁和悲观锁的各实现方式比较多,这里选择比较常用的实现方式进行演示

版本号机制
乐观锁的版本号实现机制大概是这样的流程,一般是在数据表中加上一个数据版本号version字段(字段可以随便取,你能明白它是表示版本号的意思就行),表示数据当前的版本号,当数据被修改时,version值会发生该改变,一般是在原来的数值上递增加一,当线程A要更新数据时,在读取数据的同时会将version字段值读取出来,在提交更新时,若刚才读取到的version值与当前数据库中的version值相等时,就说明此条数据没有被别的线程修改,这时会对数据进行更新

假设数据库中存在商品表goods,表中有以下数据
image.png
 

这里的德芙的数量还有最后1条,按照正常的情况用户去购买的时候只能有一个用户购买成功,如果不使用版本号的话,在有多个用户同时购买的场景下会有以下图示中的问题

image.png

图画的是稍微寒碜了点,但是意思是这么个意思

如果引入了版本号机制就能够解决这个问题,用户A和用户B同时下单查询到的商品数量与版本号都为 1

image.png

 

用户A购买商品,发现商品的这条数据的版本号和开始查询出来的版本号是一致的,所以可以更新此数据,goods_num更新为0,版本号更新为2,表示数据已被修改过

UPDATE `goods` SET `goods_num`=`goods_num`-1,`version`=`version`+1 WHERE `id`=1 AND `version`='1'

image.png

这个时候用户B再去购买此商品,发现此条数据的version版本已经变成了2,和开始查询出来的版本号不一致(用户A先购买此商品),于是没有进行更新,sql返回的更新数据的条数为0,业务层就可以通过sql执行的结果给用户相应提示

UPDATE `goods` SET `goods_num`=`goods_num`-1,`version`=`version`+1 WHERE `id`=1 AND `version`='1'

 

image.png

 

行锁(以Mysql为例)

Mysql数据库有四种引擎,MyISAM,InnoDB,MEMORY,MERGE
常用的引擎是MyISAMInnoDB

  • MyISAM只支持表锁,且不支持事务

  • InnoDB支持事务,且支持四种隔离级别(读未提交、读已提交、可重复读、串行化,及支持表锁和行锁,不过行锁是在命中索引的情况下才会起作用,否则会锁住表,导致其他事务不能够对此表进行操作

这里的goods表引擎为InnoDB,且id为主键索引,所以是支持行锁(排他锁)的
Mysql中提供 for update 来对行数据进行加锁

为了实现Mysql行锁的效果,我使用两个Mysql客户端命令行来进行演示,不在业务层代码实现

还是假设用户A和用户B同时购买德芙为例

以下是完整演示流程:

  1. 客户端A 开启事务
  2. 查询goods表中id等于1的德芙,并且锁住此行数据
  3. 更新goods表中id等于1的德芙数量
  4. 客户端A先不提交事务
  5. 客户端B 开启事务
  6. 尝试查询goods表中id等于1的德芙,并且锁住此行数据
  7. 客户端A 提交事务
  8. 客户端B的再次执行5,6步骤

客户端A开启事务,查询goods表中id为1的德芙数量,且更新德芙的数量,暂时不提交事务
image.png

客户端B同时也开启事务,查询goods表中id为1的德芙数量,我们会发现,由于此行数据已被客户端A上锁,导致等待客户端A锁释放超时,注意,在行数据被锁住的时候是可以进行查询的,这里查询超时是因为加了 for update

image.png

这个时候我们将客户端A的事务提交,释放锁,观察发现客户端A的事务提交成功,goods表中id为1的德芙数量也正常减去了1
image.png

image.png

然后让客户端B去重新查询goods表中id为1的德芙数量,发现查询到的数量为0,达到了预期的效果,由于现在此行数据没有锁住,所以客户端B是可以正常对此行数据进行操作的,但是德芙数量为0,所以代码层应该提示用户不能再进行购买了
image.png

END
以上是对乐观锁及悲观锁的理解和总结,文中如有不正确的地方,还希望大佬们指出!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值