实战:使用MySQL乐观锁解决库存超卖问题
创作时间:
作者:
@小白创作中心
实战:使用MySQL乐观锁解决库存超卖问题
引用
CSDN
1.
https://blog.csdn.net/jike11231/article/details/124304206
在高并发场景下,如何避免库存超卖问题?本文将通过一个具体的案例,演示如何使用MySQL乐观锁来解决这一问题。通过Goods和Order两个实体类,以及相应的Dao和Service层实现,配合单元测试验证,完整展示了乐观锁在实际项目中的应用。
在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术解决方案的,比如悲观锁,分布式锁,乐观锁,队列串行化,Redis原子操作等。本篇通过MySQL乐观锁来演示基本实现。
一、Goods和Order
@Data
public class Goods {
private int id;
private String name;
private int stock;
private int version;
}
@Data
public class Order {
private int id;
private int uid;
private int gid;
}
二、OrderDao和GoodsDao
@Mapper
public interface OrderDao {
/**
* 插入订单
* 注意: 由于order是sql中的关键字,所以表名需要加上反引号
* @param order
* @return int
*/
@Insert("INSERT INTO `order` (uid, gid) VALUES (#{uid}, #{gid})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertOrder(Order order);
}
@Mapper
public interface GoodsDao {
/**
* 查询商品库存
* @param id 商品id
* @return
*/
@Select("SELECT * FROM goods WHERE id = #{id}")
Goods getStock(@Param("id") int id);
/**
* 乐观锁方案扣减库存
* @param id 商品id
* @param version 版本号
* @return
*/
@Update("UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = #{id} AND stock > 0 AND version = #{version}")
int decreaseStockForVersion(@Param("id") int id, @Param("version") int version);
}
三、GoodsService
@Service
@Slf4j
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
@Autowired
private OrderDao orderDao;
/**
* 扣减库存
* @param gid 商品id
* @param uid 用户id
* @return SUCCESS 1 FAILURE 0
*/
public int sellGoods(int gid, int uid) {
int retryCount = 0;
int update = 0;
// 获取库存
Goods goods = goodsDao.getStock(gid);
if (goods.getStock() > 0) {
// 乐观锁更新库存
// 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
// 最多重试3次
while(retryCount < 3 && update == 0){
update = this.reduceStock(gid);
retryCount++;
}
if(update == 0){
log.error("库存不足");
return 0;
}
// 库存扣减成功,生成订单
Order order = new Order();
order.setUid(uid);
order.setGid(gid);
int result = orderDao.insertOrder(order);
return result;
}
// 失败返回
return 0;
}
/**
* 减库存
*
* 由于默认的事务隔离级别是可重复读,会导致在同一个事务中查询3次goodsDao.getStock()
* 得到的数据始终是相同的,所以需要提取reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
*/
@Transactional(rollbackFor = Exception.class)
public int reduceStock(int gid){
int result = 0;
//1、查询商品库存
Goods goods = goodsDao.getStock(gid);
//2、判断库存是否充足
if(goods.getStock() >= 0){
//3、减库存
// 乐观锁更新库存
result = goodsDao.decreaseStockForVersion(gid, goods.getVersion());
}
return result;
}
}
四、单元测试GoodsServiceTest
@SpringBootTest
class GoodsServiceTest {
@Autowired
GoodsService goodsService;
@Test
void seckill() throws InterruptedException {
// 库存初始化为10,这里通过CountDownLatch和线程池模拟100个并发
int threadTotal = 100;
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadTotal);
for (int i = 0; i < threadTotal ; i++) {
int uid = i;
executorService.execute(() -> {
try {
goodsService.sellGoods(1, uid);
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
}
五、测试结果
库存由10减到了0,并且生产了10条订单记录。
参考资料
更新库存数量 - 乐观锁
通过乐观锁解决库存超卖的问题
mysql 乐观锁_使用MySQL乐观锁解决超卖问题
商品超买超卖问题分析及实战
热门推荐
肚子疼究竟是哪里疼?医生说这几种肚子疼格外危险!
东莞教你拍出超有年味儿的除夕大片!
用AI设计精美新年剪纸贺图
国家图书馆贺除夕图片展:一场传统与创新的视觉盛宴
冬至必吃:猪肉大葱饺子新花样
香辣爽口,皮蛋新吃法——擂椒皮蛋制作步骤详解
从零开始设计虎年贺卡:创意与心意的完美融合
2025新年祝福语大全:从传统到创意,总有一款适合你
《封神第一部:朝歌风云》:现代化的神话书写
宠物:家庭情感健康的守护者
圣诞节的由来和习俗:西方国家如何过圣诞节
捷达车主必看:CD机蓝牙改造攻略
秋日打卡:八达岭长城+颐和园一日游
冬季感冒高发!这些感冒药成分你用对了吗?
感冒灵和连花清瘟,谁才是你的冬日救星?
历法择吉之十二建除日
春节全家总动员,写对联迎新年!
创意春联走红:传统习俗的年轻化表达
春节将至,教你如何在家贴出最潮春联!
太极宫:隋唐两朝的政治中心与建筑瑰宝
卡罗拉1.2T先锋版:车载收音机频率设置全攻略
江西米粉有哪些种类 江西什么米粉比较出名
天津最美摄影点:海河沿线、民园体育场、先农大院
狗不理包子:从天津街头小吃到国际美食名片
天津亲子游完全攻略:从刺激的过山车到精彩的海洋生物表演
重庆成都必打卡美食:严太婆锅魁、张老二凉粉、老码头火锅
五星级酒店鲍鱼烹饪秘诀大公开:从选材到调味的全流程解析
元旦打卡重庆成都必去景点
重庆洪崖洞&成都熊猫基地:网红打卡必去景点全攻略
清蒸鲍鱼:新年餐桌上的C位担当!