springboot-seckill icon indicating copy to clipboard operation
springboot-seckill copied to clipboard

关于乐观锁

Open Lxiaolong opened this issue 6 years ago • 19 comments
trafficstars

本人小白,mysql的update本身具有排他锁,@Update("update sk_goods_seckill set stock_count = stock_count - 1, version= version + 1 where goods_id = #{goodsId} and stock_count > 0 and version = #{version}"),更新库存应该不存在安全问题,为什么还需要根据版本来实现乐观锁,希望释疑。

Lxiaolong avatar May 06 '19 08:05 Lxiaolong

你说的属于悲观锁解决超卖方案,每次更新前要select for update给指定商品信息加排他锁,然后阻塞其他请求,但这种情况下并发性能大大降低。

zaiyunduan123 avatar May 08 '19 11:05 zaiyunduan123

try { sg.setVersion(goodsMapper.getVersionByGoodsId(goods.getId())); ret = goodsMapper.reduceStockByVersion(sg); } 我比较好奇的是这里,我觉得第一个语句多余,你根据id获取版本号,但是版本号是为了乐观锁,而你在更新时候的时候 @Update("update sk_goods_seckill set stock_count = stock_count - 1, version= version + 1 where goods_id = #{goodsId} and stock_count > 0 and version = #{version}")还是直接使用更新语句,我的意思是你更新的时候可以直接更新@Update("update sk_goods_seckill set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0")不需要select for update,因为不需要查询什么东西是我哪里理解有问题嘛?希望指教!

[email protected]

发件人: 江云雄 发送时间: 2019-05-08 19:30 收件人: zaiyunduan123/springboot-seckill 抄送: Lxiaolong; Author 主题: Re: [zaiyunduan123/springboot-seckill] 关于乐观锁 (#24) 你说的属于悲观锁解决超卖方案,每次更新前要select for update给指定商品信息加排他锁,然后阻塞其他请求,但这种情况下并发性能大大降低。 — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

Lxiaolong avatar May 09 '19 03:05 Lxiaolong

乐观锁发生冲突时版本号已经被其他请求+1,所以重试时需要获取最新的版本号,直接update还是用原来的版本号,会一直重试失败。

zaiyunduan123 avatar May 09 '19 06:05 zaiyunduan123

我的意思是不用版本号,直接 @Update("update sk_goods_seckill set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0")

[email protected]

发件人: 江云雄 发送时间: 2019-05-09 14:05 收件人: zaiyunduan123/springboot-seckill 抄送: Lxiaolong; Author 主题: Re: [zaiyunduan123/springboot-seckill] 关于乐观锁 (#24) 乐观锁发生冲突时版本号已经被其他请求+1,所以重试时需要获取最新的版本号,直接update还是用原来的版本号,会一直重试失败。 — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

Lxiaolong avatar May 09 '19 06:05 Lxiaolong

直接update还是会导致超卖问题,如果需要用排他锁,update之前要select for update申请排他锁才可以。

zaiyunduan123 avatar May 09 '19 06:05 zaiyunduan123

直接update会默认X锁,我用mysqlslap 并发测试了,不会出现超卖情况

[email protected]

发件人: 江云雄 发送时间: 2019-05-09 14:35 收件人: zaiyunduan123/springboot-seckill 抄送: Lxiaolong; Author 主题: Re: [zaiyunduan123/springboot-seckill] 关于乐观锁 (#24) 直接update还是会导致超卖问题,如果需要用排他锁,update之前要select for update申请排他锁才可以。 — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

Lxiaolong avatar May 09 '19 07:05 Lxiaolong

我又测试出一个让我很不解的结果。直接update和用乐观锁实现都不会出现超卖,但是效率有很大区别

第一条是直接更新,利用mysql的X锁实现不超卖,第二条相当于实现一个乐观锁,但是执行update的时候还是会使用X锁,但是效率更快了,这是什么原因造成的 可以解答一下吗,谢谢

[email protected]

发件人: [email protected] 发送时间: 2019-05-09 15:32 收件人: zaiyunduan123/springboot-seckill 主题: Re: Re: [zaiyunduan123/springboot-seckill] 关于乐观锁 (#24) 直接update会默认X锁,我用mysqlslap 并发测试了,不会出现超卖情况

[email protected]

发件人: 江云雄 发送时间: 2019-05-09 14:35 收件人: zaiyunduan123/springboot-seckill 抄送: Lxiaolong; Author 主题: Re: [zaiyunduan123/springboot-seckill] 关于乐观锁 (#24) 直接update还是会导致超卖问题,如果需要用排他锁,update之前要select for update申请排他锁才可以。 — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

Lxiaolong avatar May 09 '19 09:05 Lxiaolong

stock_count > 0 这个条件已经是乐观锁了。根本不会出现超卖现象。那个版本号原理就是cas,在这里有点多此一举。同时redis是单线程,根本不会有两个线城同时减库存,所以不可能存在超卖现象

wenbochang888 avatar May 22 '19 07:05 wenbochang888

确实是这样的,因为加了库存判断,这就已经解决超卖问题,是我理解有误。

zaiyunduan123 avatar May 22 '19 07:05 zaiyunduan123

学习了,谢谢,刚刚我也是在这个环境又疑惑,5次判断乐观锁,如果数量不是 更新减一,而是用户自己下单的数量减N,那这一步是比较好的

worlse avatar Jul 31 '19 07:07 worlse

afterPropertiesSet();这个同步mysql和redis库存的操作,因为mysql和redis的速率不同导致redis上的库存已经用完但是mysql还没来得及更新,然后afterPropertiesSet()会把redis上的库存同步为mysql上一样的,会不会产生大量的redis上的超卖

fang179364 avatar Aug 01 '19 03:08 fang179364

afterPropertiesSet();这个同步mysql和redis库存的操作,因为mysql和redis的速率不同导致redis上的库存已经用完但是mysql还没来得及更新,然后afterPropertiesSet()会把redis上的库存同步为mysql上一样的,会不会产生大量的redis上的超卖

不会,这个同步是初始化的时候同步,redis库存用完意味着此次秒杀结束

Lxiaolong avatar Aug 01 '19 06:08 Lxiaolong

long stock = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if (stock < 0) { afterPropertiesSet(); long stock2 = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if(stock2 < 0){ localOverMap.put(goodsId, true); return Result.error(CodeMsg.SECKILL_OVER); } } 但是代码上写的逻辑就是redis上的库存一为0就会更新mysql上的缓存数量

fang179364 avatar Aug 01 '19 06:08 fang179364

long stock = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if (stock < 0) { afterPropertiesSet(); long stock2 = redisService.decr(GoodsKey.getGoodsStock, "" + goodsId);//10 if(stock2 < 0){ localOverMap.put(goodsId, true); return Result.error(CodeMsg.SECKILL_OVER); } } 但是代码上写的逻辑就是redis上的库存一为0就会更新mysql上的缓存数量

昂,你应该这样理解,超卖是出现数据库为负数的情况,所以就算redis缓存同步为上次一样的也没关系,这样的后果只是多一些人来参加秒杀,但是不会出现超卖,这些人最后的结果的是秒杀失败。

Lxiaolong avatar Aug 01 '19 06:08 Lxiaolong

我在想redis上如果不出现超卖,那mybatis上是不是也不会超卖,然后mybatis上是不是就不用加锁了

fang179364 avatar Aug 01 '19 06:08 fang179364

我在想redis上如果不出现超卖,那mybatis上是不是也不会超卖,然后mybatis上是不是就不用加锁了

mysql更新操作默认加X锁,数据库主要控制好事务,下单,减库存要加事务。还有我估计这里同步还是考虑到秒杀取消订单的情况。redis单线程,你直接实现不超卖也可以。

Lxiaolong avatar Aug 01 '19 07:08 Lxiaolong

谢谢大佬代码学习了

fang179364 avatar Aug 01 '19 07:08 fang179364

hello,你是如何测试的,然后确认的乐观锁性能要比悲观锁性能高, 并发多少 , 我看了最早的你的想法 我觉得思路是对的 , 不太确认为什么你的测试结果却不好。 @Lxiaolong

lzhenglin avatar Jul 04 '23 11:07 lzhenglin

这个如果只有乐观锁去判断的话,那不是秒杀的时候失败的概率会很高?

1gxv1 avatar Apr 18 '24 14:04 1gxv1