“秒杀”问题的数据库和SQL设计
发布于 2021-01-12 14:33
1. 问题的来源
一定要高性能,不然还能叫秒杀吗?
要强一致性,库存只有100个,不能卖出去101个吧?但是库存10000实际只卖了9999是否允许呢?
既然这里说了是秒杀,那往往还会针对每个用户有购买数量的限制。
下文的所有解决方案是在 Mysql InnoDB 下做的。因为用到了很多数据库特性。其他的数据库或其他的数据库引擎会有不同的表现,请注意。
2.完全不考虑一致性的方案
2.1 表结构![](https://weixin.aisoutu.com/cunchu4/1/2021-01-12/1_16104379226382067.png)
2.2 方案
user
和 deal
的关联表。谁买了多少就插入数据呗。buy_count
是否超过单人购买限制。select sum(buy_count) from UserDeal where deal_id = ?
select count(*) from UserDeal where user_id = ? and deal_id = ?
insert into UserDeal (user_id, deal_id, buy_count) values (?, ?, ?)
2.3存在的问题
如果库存只剩一个,两个用户同时点购买,两个人检查全部成功,最后,就超卖了。
如果一个用户同时发起两次请求,检测部分同样可能会同时通过,最后,数据就异常了。
3.保证单用户不会重复购买
alter table UserDeal add unique user_id_deal_id(user_id, deal_id)
4. 解决超卖问题
4.1 方案
select
语句没有使用 for update
关键字,所以就算加入了事务也不会影响其他人读写。select
语句即可:select sum(buy_count) from UserDeal where deal_id = ? for update
4.2 优化
deal
也会相互影响呢?select
语句中的查询条件是 where deal_id = ?
,你以为只会锁所有满足条件的数据对吧?alter table UserDeal add index ix_deal_id(deal_id)
05. 提高性能了
06. 鱼与熊掌不可兼得
6.1 优化的思路
6.2 秒杀可以容忍什么
7. 为了性能牺牲一致性的设计方案
7.1 去掉了事务会发生什么
for update
锁行就无效了,我们可以另辟蹊径,来解决这个问题。7.2 修改表结构
Deal
表,其实它就是存了一下基本信息,包括最大售卖量。sum(buy_count)
操作来得到已经卖掉的数量的,然后进行判断后再进行插入数据。Deal
表,把已经售卖的量也存放在 Deal
表中,然后巧妙地把操作转换成一行 update
语句。![](https://weixin.aisoutu.com/cunchu4/1/2021-01-12/1_16104411517532065.png)
7.3 修改执行过程
update
语句来了:update Deal set buy_count = buy_count + 1 where id = ? and buy_count + 1 <= buy_max
buy_max
是1000,如果有2000个用户同时操作会发生什么?update
语句天然会有行锁,前1000个用户都会执行成功,返回生效行数1。而剩下的1000人不会报错,但是生效行数为0。update
语句的生效行数就知道是否抢购成功了7.4 还没有结束
update
的生效行数是1,就代表购买成功。所以,如果一个用户购买成功了,那么就再去 UserDeal
表中插入一下数据。Deal
表把刚才购买的还回去:update Deal set buy_count = buy_count - 1 where id = ? and buy_count - 1 >= 0
buy_count - 1 < 0
的情况,除非你实现的不对。![](https://weixin.aisoutu.com/cunchu4/1/2021-01-12/1_16104391258992066.png)
![](https://weixin.aisoutu.com/cunchu4/1/2021-01-12/1_16104349668642066.png)
执行成功
无库存
回滚成功
损失库存
8. 不要过度优化
本文作者:璀璨小二
原文:https://www.cnblogs.com/clphp/
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材