一、业务对象或锁对象是多例的情况下
原因:业务中一般使用的lock对象锁,lock锁的范围是针对同一个对象里面不同的线程,也就是说,jvm锁是对象锁,对象之间锁不共用
解决方案:保证业务对象和锁对象是单例
二、在使用了spring事物注解的情况下(不单是jvm锁,大部分锁实现都会出现这个问题)
原因:spring事务是基于aop的方式实现的,是包裹着整个方法的(包括锁),事务不在锁的范围内,很容易出现并发执行的时候,a方法的事务还没提交上去,b事务就读了数据库的旧值。
举个例子:
代理类中的方法实际代码是这样的 :
a事务还没提交,b事务就读取了数据库的旧值(流程图):
解决方案:
第一种: 用显示事务,将事务放在锁范围里面
第二种: 事务隔离级别改为读未提交(不推荐)
第三种: 再套一层方法,在外层方法使用锁
第四种: 最优雅的解决方法,将锁封装成注解的形式,并把优先级设置成比事务注解低
三、在服务集群的情况下
原因:服务都不一样了,锁和对象自然也不一样(就和第一个情况下的环境一样)
解决方法:利用mysql的排他锁机制,将所有业务sql集中成一条sql(以上三种问题都能解决,但是不灵活,只能在业务允许的情况下使用)
总结
综上所述,我们可以发现jvm锁只适合在单体项目中并且业务需求简单的情况下使用,所以有条件还是使用分布式锁吧。
2.一个sgl语句:更新数量时判断解决:解决了上面三个锁失效的问题,但是它是表级锁,这种是不能接受的,我要买多种商品结果你把表锁了,整张表都不能并发了、性能肯定就是不行的,最好使用行级锁。 问题: 1 锁范围问题:是表级锁还是行级锁 2.同一个商品有多条库存记录:仓库有多个、商品ID是一个,可以根据算法减库存、一个sql语句做不到 3.无法记录库存变化前后的状态
使用一个SQL语句时的优缺点
优点:一个sgl语句:更新数量时判断解决:解决了上面三个锁失效的问题
缺点:
1 锁范围问题:是表级锁还是行级锁
2.同一个商品有多条库存记录:仓库有多个、商品ID是一个,可以根据算法减库存、一个sql语句做不到
3.无法记录库存变化前后的状态
悲观锁的范围:表级锁还是行级锁?
我们有一张数据库表:里面有三条数据,商品1001分别在北京仓和上海仓、商品1002在深圳仓
我们首先模拟A用户开启事物、更新1001商品,让库存减1
如上图所示:此时2条数据更新成功,但是事物还没提交;
然后使模拟B用户开启事物、更新1002商品,让库存减1
此时我们就验证了mysql是表级锁
我们使用:commit 提交事物;或者ROLLBACK回滚事物;用户B的更新动作才会成功
很显然,一个sgl语句:更新数量时判断解决:解决了上面三个锁失效的问题,但是它是表级锁,这种是不能接受的,我要买多种商品结果你把表锁了,整张表都不能并发了、性能肯定就是不行的,最好使用行级锁。
mysql悲观锁中如何使用行级锁:
1、锁的查询或者与新条件必须是索引字段
2、查询或者更新条件必须是具体值;使用 =、in,不能使用like 、!=
我们在product_code字段上新建索引、然后重复上面步骤、发现是可以的、结果是行级锁、用户A和用户B执行更新互不影响;
注意:使用行级锁必须满足以上两个条件
悲观锁:select ... for update
mysq使用乐观锁实现超卖
mysql乐观锁:借助时间戳、或者version版本号实现,需要依赖CAS这种机制
首先需要在表中增肌version字段,如下:
先查询:select * from distributed_lock where product_code = 1001
查询到的数据为2条、我们就取id为1的
此时product_code = 1001,id = 1 的商品的version为0,stock_count = 4997
我们执行修改动作
update distributed_lock set stock_count = stock_count-1,version = version+1 where id = 1 and version = 0,如果修改之间被用户B修改了、用户A修改成功影响的行数为0,此时我们会做重试的机制、直到它修改成功影响的行数变为1.
mysq!锁总结
性能:一个sql>悲观锁>jvm锁>乐观锁
如果追求极致性能、业务场景简单并且不需要记录数据前后变化的情况下。
优先选择:一个sql!
如果写并发量较低(多读),争抢不是很激烈的情况下优先选择:乐观锁
如果写并发量较高,一般会经常冲突,此时选择乐观锁的话,会导致业务代码不间断的重试
优先选择:mysql悲观锁
不推荐ivm本地锁。