如何利用MySQL实现秒杀

本文采用MySQL下的InnoDB存储引擎实现秒杀,MySQL支持很多存储引擎,每种存储引擎都有不同的特性。若使用的是其他存储存储引擎或者数据库情况将会有所不同。

秒杀有以下几种特点:

1、不能出现超卖。假设秒杀的商品只有100个库存,结果你卖出了101个,甚至更多,在这种情况下,你的秒杀也谈不上秒杀。

2、一个用户只能买一个商品。

3、高性能,直观感受就是快,否则就谈不上秒杀。

首先我们简单的模拟一下场景,建表。

CREATE TABLE `sdb_goods_promotion` (
  `goods_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `stock` int(10) unsigned NOT NULL,
  PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

促销商品表:负责存储需要促销的商品,以及库存量。

CREATE TABLE `sdb_goods_promotion_buyer` (
  `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `goods_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

买家订单表:记录买家的购买信息。

大部分人都会这么做:

begin transaction

已卖出数量:select count(goods_id) from sdb_goods_promotion_buyer where goods_id=xxxx

总库存量:select stock from sdb_goods_promotion where goods_id=xxxx

if(已卖出数量>=总库存量){

rollback

}

当前用户已买数量:select count(goods_id) from sdb_goods_promotion_buyer where uid=xxx;

if(当前用户已买数量>=购买限制){

rollback

}

insert into sdb_goods_promotion_buyer(uid,goods_id) value(XXX,XXXX);

commit;

像上面这种做法,把数据读到内存中进行运算,最终入库的操作,既不能避免用户购买次数限制,也不能避免超卖情况。

 

接下来,我们一一解决购买次数限制以及超卖情况。

1、用户多次购买问题

我们只需要在买家订单表里面添加一个唯一索引(uid,goods_id)就可以解决这个问题。当有用户重复购买时就会跑出异常。从而在数据库层面解决了这个问题。但在代码中我们需要捕获(catch)这个异常,抛出用户便于理解的信息。 

2、超卖问题

在sdb_goods_promotion表的goods_id列上加上索引,然后利用悲观锁。select * from sdb_goods_promotion where goods_id=xxx for update;

以上我们就解决了用户多次购买和超卖问题。

但是,如果我们用户体量有几十万或者更大的时候,这种做法是很慢,且不合理的。所以我们还是得继续优化。

 

在上面的例子中我们为了保证一致性而引入了事务和悲观锁,悲观锁是一种很耗时的操作。但是现在我们为了性能却要牺牲一致性了。

在秒杀中,我们要知道什么能接受 ,什么不能接受。比如在上面的例子中我们不能接受一个用户买多个商品,也不能接受超卖,但是我们是可以接受少卖的。只要我们把少卖的商品误差做到足够小。

在上面的例子中,用户重复购买是不会发生的,因为我们使用MySQL的唯一索引实现。我们为了性能去掉了事务,所以 for upate锁就自然失效了。我们之前使用count()这种方式统计已卖出的数量,然后在内存中进行运算得出剩余的商品数量,进行订单的生成操作。但是现在我们去掉了事务,就再也不能保证一致性了。

所以,我们接下来可以修改一下商品促销表(sdb_goods_promotion):

新增一列already_sales(已卖出的商品数量),表结构如下:

CREATE TABLE `sdb_goods_promotion` (
  `goods_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `already_sales` int(10) unsigned NOT NULL DEFAULT '0',
  `stock` int(10) unsigned NOT NULL,
  PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

接下来当有用户进行购买时,我们的库存减少操作如下:

update sdb_goods_promotion set already_sales=already+1 where goods_id=XXXX

如果商品只有1000个,但是同时有2000个人抢购会发生什么情况?

虽然没有了事务,但是update语句会天然的有行锁,前1000个用户都会执行成功,返回生效行数为1,后1000个用户都会失败,虽然不会失败,但是返回生效行数为0。所以在程序中只要判断update的生效行数就知道是否抢购成功了。

但是我们在库存减少和订单生成应该是要保持一致性的,所以当用户重复购买生成订单失败时,我们需要把扣减的商品数量加上去。

$res = update sdb_goods_promotion set already_sales=already+1 where goods_id=XXXX and stock>already_sales

if($res){

try{

insert into sdb_goods_promotion_buyer(uid,goods_id) value(XXX,XXXX);

}catch(\Exection $exection){

update sdb_goods_promotion set already_sales=already-1 where goods_id=XXXX and already_sales-1>0

}

}else{

echo "商品已卖完";exit;

}

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值