mysql超卖问题处理_高并发下超卖问题及如何解决

本文探讨了电子商务场景中高并发下的库存控制问题,包括如何预防库存超卖现象,提出了使用MySQL事务、乐观锁及Redis缓存等解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般电子商务网站会有团购,秒杀等活动,而这样的活动特点是请求量激增,数以万计的用户会抢购一个商品,这样会面临活动商品库存有限,高并发下如何控制库存不出现超卖的问题。

注意:

我们的数据存放在MySQL中

使用的语言是Java

为何会发生超卖

一般库存扣除的逻辑代码如下:

//remainder为剩余库存数量

int remainder=statement.query("select remainder from stock where stock_id='$STOCK_ID$'");

//amount为本次订单抢购数量

if(remainder>=amount!=0){

statement.execute("update stock set remainder=remainder-amount where stock_id='$STOCK_ID$'")

}

防止超卖我们首先想到的是通过事务去解决这个问题:

startTransaction();//开启事务

try{

//remainder为剩余库存数量

int remainder=statement.query("select remainder from stock where stock_id='$STOCK_ID$'");

//amount为本次订单抢购数量

if(remainder>=amount!=0){

statement.execute("update stock set remainder=remainder-amount where stock_id='$STOCK_ID$'")

}

}catch(Exception e){

rollback();//回滚

}

commit();//提交事务

上面的事务下执行逻辑其实隐藏一个很大的漏洞-有可能库存会变为负数(即超卖),具体分析如下:

由于是高并发,假设有三个用户a,b,c同时抢购该物品,并进入到了这个事务中,这三个用户查到的库存数是一样的(MySQL rr级别下总是读取事务开始时的行数据)

然后进入到update,假设这三个用户同时进入update操作,这个时候由于 行级锁的排他性限制,MySQL会将update操作串行化

上面update执行完后,有可能会发生库存变为负数的情况(超卖)

怎么解决超卖

最简单的方式("锁方式")

针对上述超卖的情况我们可以通过更改下执行sql代码来实现:

startTransaction();//开启事务

try{

statement.execute("update stock set remainder=remainder-amount where stock_id='$STOCK_ID$' and $remainder>=$amount")

}

}catch(Exception e){

rollback();//回滚

}

commit();//提交事务

上面的修改可以杜绝库存超卖的现象。注意以上在MySQL一致性非锁定读(rr隔离级别下)。

针对以上,我们可以换个思路,在库存数据结构中加入version字段来控制记录修改版本,也可以解决上述问题,如下:

startTransaction();//开启事务

try{

//remainder为剩余库存数量

int remainder=statement.query("select remainder,version from stock where stock_id='$STOCK_ID$'");

//amount为本次订单抢购数量

if(remainder>=amount!=0){

statement.execute("update stock set remainder=remainder-amount,version=version+1 where stock_id='$STOCK_ID$' and version=$version")

}

}catch(Exception e){

rollback();//回滚

}

commit();//提交事务

但是我们真的能这么做吗?我们的业务可是高并发,面对的是1万+TPS,那如果按上面继续执行会遇到什么问题?

很显然不能,在高并发下,会有很多这样的修改(update),每个请求都需要等待"锁",某些请求可能永远都获取不到锁,这种请求就会卡在那里,直到超时。同时,由于这种写请求很多,会造成大量的请求超时,连锁反应就是应用系统连接数被耗光,直至系统异常crash。即使重启系统,由于请求量大,系统也会立马挂掉。

高并发下如何解决超卖

引入缓存

主要思路是:

首先在团购秒杀开始前将需要的物品库存信息放入缓存中

使用锁来处理其并发请求

将缓存中的数据同步到数据库。

我们此处使用redis作为缓存。

应用操作redis减库存的大体思路为:

首先通过redis api监听相关物品的库存信息,在事务开启前保证该物品库存信息无人修改

获取现有库存信息,判断库存不为0并且当前库存量大于等于订单所需数量

满足上述2的话则进行扣除操作

如果在1的过程中有别人更新了该物品库存信息版本,则重试

知道库存为0或者剩余库存不满足当前订单扣除数量退出

具体代码如下:

public void secondBuyProduct(Jedis jedis, String stockId, int orders) {

//CAS重试

while (true) {

try {

//监视key,如果在后续事务执行之前key的值被其他命令所改动,那么事务将被打断

jedis.watch(stockId);

int prdNum = Integer.parseInt(jedis.get(stockId));

//判断库存是否满足订单数量要求

if (prdNum > 0 && prdNum - orders >= 0)) {

Transaction transaction = jedis.multi();

//减库存并写入

transaction.set(stockId, String.valueOf(prdNum - orders));

List res = transaction.exec();

//事务提交后如果为null,说明key值在本次事务提交前已经被改变,本次事务不执行。

if (res != null && !res.isEmpty()) {

System.out.println("抢购成功!");

break;

}

} else {

System.err.println("被抢光了!");

break;

}

} catch (Exception e) {

System.err.println("抢购出错:" + e.toString());

e.printStackTrace();

} finally {

jedis.unwatch();

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值