最近在看秒杀相关的项目,针对防止库存超卖的问题,查阅了很多资料,其解决方案可以分为悲观锁、乐观锁、分布式锁、Redis原子操作、队列串行化等等,这里进行浅显的记录总结。

首先我们来看下库存超卖问题是怎样产生的:

1

2

3

4

5

6

//1.查询出商品库存信息

select stock from t_goodswhere id=1;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id)values (null,1);

//3.修改商品库存

update t_goodsset stock=stock-1where id=1;

在高并发场景下,如果同时有两个线程a和b,同时查询到商品库存为1,他们都认为存库充足,于是开始下单减库存。如果线程a先完成减库存操作,库存为0,接着线程b也是减库存,于是库存就变成了-1,商品被超卖了。

下面让我们来看看针对库存超卖问题的解决方案;

解决方案一:悲观锁

所谓悲观锁,即悲观的认为自己在操作数据库时,会大几率出现并发,于是在操作前会先进行加锁,操作完成后再释放锁。如果加锁失败说明该记录正在被修改,那么当前操作可以等待后尝试。

以我们常用的MySQL为例,行锁、表锁、排他锁等都是悲观锁,为避免冲突,会在操作时先加锁,其他线程必须等待它的完成。

这里我们通过使用select...for update语句,在查询商品表库存时将该条记录加锁,待下单减库存完成后,再释放锁。

1

2

3

4

5

6

7

8

9

10

//0.开始事务

begin;/begin work;/starttransaction; (三者选一就可以)

//1.查询出商品信息

select stock from t_goodswhere id=1for update;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id)values (null,1);

//3.修改商品stock减一

update t_goodsset stock=stock-1where id=1;

//4.提交事务

commit;

这样可以解决并发时库存超卖的问题,然而高并发时,所有的操作都被串行化了,效率很低,将严重影响系统的吞吐量。而且使用悲观锁还有可能造成死锁问题。

解决方案二:乐观锁

现在我们尝试下使用乐观锁,所谓乐观锁,是相对于悲观锁而言的,它假设数据一般情况下不会发生并发,因此不会对数据进行加锁,操作完成提交时才对数据是否冲突进行检测,如果发现冲突则返回错误。

比较常见的实现方式是,在表中增加一个version字段,操作前先查询version信息,在数据提交时检查version字段是否被修改,如果没有被修改则进行提交,否则认为是过期数据。

1

2

3

4

5

6

//1.查询出商品信息

select stock, version from t_goodswhere id=1;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id)values (null,1);

//3.修改商品库存

update t_goodsset stock=stock-1, version = version+1 where id=1, version=version;

这样,在并发时,如果线程a尝试修改商品库存时,发现版本号已经被线程b修改了,线程a执行update语句条件不满足便不再执行了,库存也不会被超卖。

但是这种乐观锁的方式,在高并发时,只有一个线程能执行成功,会造成大量的失败,这给用户的体验显然是很不好的。

这里我们可以减小锁的颗粒度,最大程度提升系统的吞吐量,提高并发能力:

1

2

//修改商品库存时判断库存是否大于0

update t_goodsset stock=stock-1where id=1 andstock>0;

上面的update语句通过stock>0进行乐观锁的控制,在执行时,会在一次原子操作中查询stock的值,并扣减一。

解决方案三:分布式锁

除了在数据库层面加锁,我们还可以通过在内存中加锁,实现分布式锁。例如我们可以在Redis中设置一个锁,拿到锁的线程抢购成功,拿不到锁的抢购失败。

Redis的setnx方法可以实现锁机制,key不存在时创建,并设置value,返回值为1;key存在时直接返回0。线程调用setnx方法成功返回1认为加锁成功,其他线程要等到当前线程业务操作完成释放锁后,才能再次调用setnx加锁成功。

Long TIMEOUT_SECOUND = 120000L;

Jedis client =jedisPool.getResource();

//线程设置lock锁成功

while(client.setnx("lock",String.valueOf(System.currentTimeMillis())) == 1){

Long lockTime = Long.valueOf(client.get("lock"));

//持有锁超时后自动释放锁

if(lockTime!=null&& System.currentTimeMillis() > lockTime+TIMEOUT_SECOUND){

client.del("lock");

}

Thread.sleep(10000);

}

......

......

client.del("lock");

解决方案四:Redis原子操作

虽然通过以上方按可以防止库存超卖,但是高并发情况下对数据库进行频繁操作,会造成严重的性能问题。因此我们必须在前端对请求进行限制。

我们可以在Redis中设置一个队列key为商品的id,队列的长度为商品库存量。每次请求到达时pop出一个元素,这样拿到元素的请求即认为秒杀成功,后续通过MQ发送消息异步完成数据库减库存操作。没有拿到元素的请求即认为秒杀失败。

由于Redis是工作线程是单线程的,而list的pop操作是原子性的,因此并发的请求都被串行化了,库存就不会超卖了。

//获取商品库存

Stringtoken= redisTemplate.opsForList().leftPop(goodsStock);

if(token==null){

log.info(">>>商品已售空");

returnsetResultError("亲,该秒杀已经售空,请下次再来!");

}

//异步发送MQ消息,执行数据库操作

sendSecondKillMsg(goodsId, userId);

...

当然除此之外还有很多其他解决方案,也有很多可以优化的地方,继续学习吧~

mysql 库存超卖_高并发下防止库存超卖的解决方案相关推荐

  1. Java实现库存防超卖_高并发场景-订单库存防止超卖

    背景 在电商系统中买商品过程,先加入购物车,然后选中商品,点击结算,即会进入待支付状态,后续支付. 过程需要检验库存是否足够,保证库存不被超卖. 场景一:买家需要购买数量可以多件 场景二:秒杀活动,到 ...

  2. mysql并发插入死锁_高并发下insert死锁 · Issue #ITUNR · baomidou/mybatis-plus - Gitee.com...

    当前使用版本(必须填写清楚,否则不予处理) springboot版本:2.0.3.RELEASE mybatis版本:3.0.5 jdk:1.8 该问题是怎么引起的?(最新版上已修复的会直接close ...

  3. 高并发下防止库存超卖解决方案

    一.概述 目前网上关于防止库存超卖,我没找到可以支持一次购买多件的,都是基于一次只能购买一件做的秒杀方案,但是实际场景中,一般秒杀活动都是支持1-5件的,因此为了补缺,写了此文,方便自己之后使用. 二 ...

  4. redis rua解决库存问题_如何解决高并发下的库存安全问题,没你想得那么复杂(附源码)...

    一. 问题 不知道大家该开发中有没有遇到这样的一个问题,在电影院购票或者去网上买东西的时候,比方说当年哪吒大电影出来的时候,那抢票相当火爆啊,一票难求,那购票系统的后台是如何保证观众能买到自己喜欢的票 ...

  5. mysql余额高并发_高并发下作余额扣减的一些经验

    前一段时间参加了优化一个老的计费系统,学习了一些高并发下做余额扣减的常用手段,也做了一些尝试,因此在这里总结记录一下. 问题描述 对于一个计费系统来说,并发问题事实上分为两类,一类是应用并发高,也就是 ...

  6. php mysql 秒杀_redis+PHP实现高并发下秒杀数据入库的问题

    在高并发下实现抢购秒杀功能中,我有一个疑问,就是数据入库的问题,什么时候入库. 设想思路: 1.判断他抢购成功了,立马把生成的订单数据写入mysql订单表,同时库存表字段减少1: 2.判断抢购成功后, ...

  7. java秒杀怎么防止超卖_秒杀系统是如何防止超卖的?

    秒杀系统介绍 秒杀系统相信网上已经介绍了很多了,我也不想黏贴很多定义过来了. 废话少说,秒杀系统主要应用在商品抢购的场景,比如: 电商抢购限量商品 卖周董演唱会的门票 火车票抢座 - 秒杀系统抽象来说 ...

  8. .net core高并发_高并发下的Node.js与负载均衡

    阅读本文约需要6分钟 大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈).上次老师跟大家分享了下浅谈前端自动化构建的相关知识,今天跟大家分享浅谈前端自 ...

  9. kdj超卖_为什么在KDJ指标的超卖区间不能卖出股票

    投资者进入股市使用的技术分析指标有很多,比如说是macd指标.rsi指标.kdj指标等都是可以判断股票的买卖点的分析指标,但是为什么股价处于KDJ指标中的超卖区间时不能进行卖出操作? 第一.KDJ指标 ...

  10. mysql乐观锁处理超卖_通过乐观锁解决库存超卖的问题

    前言 在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能.库存超卖问题是有很多种技术解决方案的,比如悲观锁,分布式锁,乐观锁,队列串行化,Redis原子操作等.本篇通过M ...

最新文章

  1. linux+npm+v+报错_linux – npm install会导致像npm ERR这样的错误...
  2. jdbc的预处理中如何处理模糊查询
  3. day14【前台】用户登录注册
  4. 【兼容】----常见浏览器兼容性问题与解决方案
  5. 史密斯圆图串并联口诀_史密斯圆图的详解
  6. python修改图像分辨率大小
  7. sparkling-water的介绍与实践(command line)
  8. 冰汽时代机器人不用热_冰汽时代机器流玩法 寒霜朋克机器人流玩法怎么玩
  9. centos7文件同步服务器,教你在 Centos7 中使用 Unison 同步文件
  10. 从还珠格格到延禧攻略,不变的是什么?
  11. 物种内共线性分析——JCVI安装以及数据下载(一)
  12. Redis为什么这么快?
  13. 在 Windows 系统下,如何将“使用VSCode打开”添加至鼠标右键菜单栏
  14. 十六进制数据的CRC16校验码自写软件
  15. 【PBR系列三】BRDF方程及渲染方程
  16. Linux进程详解 【Linux由基础到进阶】
  17. android文件打印--printerShare
  18. ESP32学习笔记(27)——BLE GAP主机端扫描
  19. 开山斧0.3.8(跨平台版本)《源码已开放》
  20. 解决:Win10 显示无法连接到Internet,但是还能上网

热门文章

  1. 汉语拼音音节的歧义指数
  2. matplotlib绘图实现中文宋体和英文Times New Roman
  3. CSS3边框图片效果
  4. 【Python】24点 一行代码解决
  5. 经典算法题:有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号。
  6. 纯电动汽车整车控制器(VCU)详细介绍
  7. java获取https网页代码_java抓取Https协议url地址的源码的方法
  8. vue-router
  9. 什么是光纤?光纤有哪些优势?
  10. VCF 文件 格式 详解