实战:使用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乐观锁解决超卖问题
商品超买超卖问题分析及实战
热门推荐
蜂蜜柚子茶
【科普】多“泻”之秋,儿童该如何防“泻”
电气工程及其自动化专业求职者怎样写好技能特长
山东:擘画海洋数字经济新蓝图
杭州雷峰塔真的有压了千年的小白
玫瑰香水不含玫瑰(玫瑰香水不含玫瑰精油)
卫生间做干湿分离还不够,现在更流行做三式分离
如何识别和避免使用有安全隐患的破解软件
揭秘华伦夫妇:超自然现象界的传奇调查员
笔记本玩游戏限30帧的解决办法
移动硬盘没有盘符怎么办?从原因分析到解决方案全攻略
猫狗拼的新命题:从下沉市场到下沉需求
面世100年!2025年重温8部文学经典
人脸图像特征提取(ArcFace)
时评:渝厦高铁助推区域经济腾飞发展
存储芯片的分类有哪些 存储芯片和逻辑芯片的区别
摩托车可以走应急车道吗?哪些地方允许?
齐齐哈尔十大春季旅游景点
小米即将上市,十万资金如何参与投资?
腰椎骨折后何时可以下床锻炼?
历史一刻!雷军成中国新首富:个人财富大揭秘
王者荣耀赵云铭文搭配攻略:攻速阈值与技能增益的完美平衡
梦见动漫角色的深层含义
明朝科学家李之藻:中西文化交流的桥梁
佛山旅游攻略:岭南文化与现代生活的完美融合
钙钛矿电池成电池产业新星,车企巨头悄然布局,未来可期?
睡觉导致脸部不对称?这些改善方法请收好
土木工程专业详解:课程设置、就业前景及留学推荐
金融业成AI替代岗位最多行业:54%任务将自动化
警惕!肝结节并非全部良性,这些结节一定要引起重视!