目录
定义
1)乐观锁
首先来看乐观锁,顾名思义,乐观锁就是持比较乐观态度的锁。就是在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁,但是在更新的时候会判断在此期间别的线程有没有更新过这个数据。
2)悲观锁
反之,悲观锁就是持悲观态度的锁。就在操作数据时比较悲观,每次去拿数据的时候认为别的线程也会同时修改数据,所以每次在拿数据的时候都会上锁,这样别的线程想拿到这个数据就会阻塞直到它拿到锁。
场景
一件二手苹果手机,成本价是800元,售价是1000元。老板先是通知小李,说你去把商品价格增加500元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到1500元,价格太高,可能会影响销量。又通知小王,你把商品价格降低300元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格1000元;小王也在操作,取出的商品价格也是1000元。小李将价格加了500元,并将1000+500=1500元存入了数据库;小王将商品减了300元,并将1000-300=700元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是700元,比成本价低100元。几分钟后,这个商品很快出售了1千多件商品,老板亏10万多。
乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,1500元,这样他会将1200元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是1200元。
模拟修改冲突数据库中增加商品表
准备一张商品表
存入苹果手机
这里以第二条数据作为例子举例
后台java模拟二人同时拿到数据并进行修改
运行测试:
可以看到最后测试结果是老板拿到的结果是700,比成本价还低了100,这样的场景肯定是不符合实际的业务需求的
乐观锁实现
数据库中添加version字段
取出记录时 获取当前信息的version
select id,商品名,商品价格,version from 商品表 where id =2
更新数据时 version+1 如果where语句中的version版本号不对 则更新失败
update 商品 set 价格 = 新的价格,`version`=`version`+1 where id = 2 and `version` = 1
mybaits-plus相关调整
后台中表相对的实例对象添加对应version字段 并添加@Verison注解
添加mybatis-plus插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
注意把刚才的价格重新改回1000再重新测试:
注意看此时的更新sql, 小李的更新sql+500成功了,小王的-300失败了,所以此时老板查到的结果应该是1500
此时可以看到,两者的修改并没有发生冲突,但是也出现了新的问题,就是小李的修改成功了,但是小王的却修改失败了,遇到这种情况的话小王可能要面临卷铺盖的风险啊,为了不让小王卷铺盖,我们这里可以为小王添加一个修改重试的功能
再次测试下,注意要把价格重新改回1000哦:
此时结果显示老板查询到的就已经是他想要的结果1200了
悲观锁
悲观锁解决方案非常简单 直接在操作sql中添加 for update语句即可
select id,商品名,商品价格,version from 商品表 where id =2 for update
使用 for update操作 可以认为是给每次操作都加上表级别的悲观锁 在事务没结束前 其他事务必须等待加锁的事务提交后才可以
注意:并发环境下都不建议使用悲观锁,因为悲观锁容易锁表,导致事务等待,性能低下。