乐观锁和悲观锁在springboot下的demo

  • Post author:
  • Post category:其他




何为乐观锁?


简而言之、言而简之,见名知意,就是锁很乐观呀,就是数据变动不会太频繁,他们们只是想看看,只是在外面蹭蹭,又没有别的心思在里面,不会对数据动手动脚的,真的要是动手动脚那可就要上锁排队来哈。简单来讲:适用于读多写少,因为只有写的时候才上锁;



何为悲观锁?


悲观锁则认为:每个人都有可能对我动手动脚,所以我在大门放一把锁,谁要进来,就得先拿到钥匙。简单来讲:就是任何事务都有可能引起数据的边动,所以事务执行之前得先拿到悲观锁才可以执行;适用于写多读少的场景;因为每次读取都去校验锁,会增加系统的开销;



撸个减库存的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这个异常,进行重试进制;重试机制我在下一篇写;



版权声明:本文为Little_Donkey_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。