实战:使用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乐观锁解决超卖问题
商品超买超卖问题分析及实战
热门推荐
银行上班工资待遇怎么样?一文为你揭秘
家庭医生推荐:4个生活建议配合食疗缓解胃胀气
柳阳春节烟花摄影指南:8个实用技巧助你拍出专业级大片
福建惠安石雕厂:简约风格墓碑设计的典范
东方夏威夷海南岛:五大景区与老年旅游优惠详解
毛泽东诞辰131周年:重温经典诗词,感受伟人风采
毛泽东诗词中的福建岁月:热血燃情,诗意盎然
金龙机电案敲警钟:六大措施筑牢企业内控防线
藏红花种植技巧大揭秘:从零开始打造“红色金子”
注册会计师把关企业资金安全:风险评估方法与案例解析
TCL查处15起职务侵占案,20人被移送司法机关
藏红花素:抗抑郁界的黑马?
西藏藏红花:高原之巅的金色传奇
国家卫健委发布会:流感病毒传播趋缓,药品供应有保障
接种流感疫苗能降低50%感染风险,专家解析有效期与保护力
流感疫苗并非一劳永逸:保护期仅4-6个月需每年接种
一文掌握多肉碧桃养殖:土壤、光照到病虫害防治
失业期间社保怎么办?两种参保方式详解
2025年失业保险新政:领取条件放宽,医疗养老保障不断档
少油多奶豆,校园饮食迎来健康变革
“闭月羞花”:文学意象与历史传说的完美融合
貂蝉形象考:历史争议与文化传承
一幅画看懂“闭月”:中国古代绘画中的貂蝉形象
低脂又美味,清蒸鸡胸肉6种创意吃法
无接亲、无车队、无堵门:年轻人的婚礼新主张
双春年:2025年迎来两个立春,民间视为丰收吉兆
旅行拍照必学:4大要点+20个实用姿势详解
挠女生脚心痒是什么征兆?探讨背后的原因!
最新预测:2024年北航录取线继续领先哈工大
避开人流玩转颐和园:亲子游路线与活动全攻略