何为乐观锁?
简而言之、言而简之,见名知意,就是锁很乐观呀,就是数据变动不会太频繁,他们们只是想看看,只是在外面蹭蹭,又没有别的心思在里面,不会对数据动手动脚的,真的要是动手动脚那可就要上锁排队来哈。简单来讲:适用于读多写少,因为只有写的时候才上锁;
何为悲观锁?
悲观锁则认为:每个人都有可能对我动手动脚,所以我在大门放一把锁,谁要进来,就得先拿到钥匙。简单来讲:就是任何事务都有可能引起数据的边动,所以事务执行之前得先拿到悲观锁才可以执行;适用于写多读少的场景;因为每次读取都去校验锁,会增加系统的开销;
撸个减库存的demo
mapper.xml
<select id="getGoodInById" parameterType="java.lang.Integer">
select
<include refid="Base_Column_List"/>
from
t_goods_in
WHERE
id = #{id}
</select>
serviceImpl.java
@Transactional(rollbackFor = Exception.class)
public void mult(int id) {
GoodsIn goodsIn = goodsInMapper.getGoodInById(id);
goodsIn.setTotalNumber(goodsIn.getTotalNumber() - 1);
goodsInMapper.updateGoodIn(JSON.parseObject(JSON.toJSONString(goodsIn), Map.class));
}
controller.java
@ApiOperation("更新库存")
@GetMapping("/mult/{id}")
public GenericResponse<Void> mult(@PathVariable(name = "id") @NotNull(message = "id不能为空") int id) throws Exception {
return ControllerTemplate.call((GenericResponse<Void> response) -> {
goodsInService.mult(id);
response.setResult(true);
});
}
t_goods_in
正常请求
接下来我用postman来模拟正常的请求,以ID=45为例;
当70个请求完成之后,我们再去看看ID= 5的库存,此时已经由70变成0了。因为postman是串行的,当前一个请求完成之后才会发第二个请求,所以这里的结果是我预期的;
非正常请求
我用Jmeter 模拟70个线程,同步去修改库存;
执行完成之后我们发现,剩余库存本应该是0的,但是却显示52,这明显不符合预期嘛,那么我们来分析一下为什么会造成这种现象?
这是因为我启动的70个线程同时进入了mult方法根据ID取获取剩余库存,然后-1更新进去,每个线程都是独立的,大家获取到的剩余库存并不是最新的。所以会出现这种结果;
如何解决
悲观锁解决办法:在sql后面加上
for update
;这也就是我前面说的,悲观锁认为每个请求都可能会去改变数据,所以每个请求进来我先把它锁住,待线程结束后才放开;大家排队一个一个来,这样就不会出错;
<select id="getGoodInById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from
t_goods_in
WHERE
id = #{id} for update
</select>
乐观锁的解决办法:我把悲观锁中的
for update
删除掉,在GoodsIn的entity类中添加version
/**
* 版本号
*/
@Version
private int version;
事务每次提交之后,我都把version+1的,所以当我们更新时,发现version不是我们上次查的那个,就不会更新;
<update id="updateGoodIn" parameterType="Map">
update t_goods_in
set
goodsType = #{goodsType},
goodsName = #{goodsName},
price = #{price},
totalNumber = #{totalNumber},
operator = #{operator},
inTime = now(),
version = version+1
where
id = #{id} and version = #{version}
</update>
所以我用Jmeter发70个并发请求,最后的库存并不是0;因为version被其他的线程更改掉了以后就更新失败了(这里不是发生异常,而是返回0条数据已被更新),所以这里我们要手动去try catch这个异常,进行重试进制;重试机制我在下一篇写;