实战:使用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乐观锁解决超卖问题
商品超买超卖问题分析及实战
热门推荐
《志明和春娇》:探索当代年轻人情感与生活的深刻爱情故事
绿色玉石:寓意、象征与风水解析,全面探索其文化意义与用途
修车技术如何提升到更高水平?高级修车方法有哪些难点?
非油炸方便面储存指南:五大要点确保品质
一直胃痛是怎么回事
预防脑中风的健康指南:从预防医学的角度出发
思辨阅读,阅读力转化为学习力的关键
韧带钙化的治疗方法
CPU使用率飙升至100%?有效解决方法
古代神话故事八仙过海
喝酒治失眠 当心越喝越清醒
《灵魂只能独行》读后感:灵魂的孤独与信仰
车贷要看几个月流水?
如何利用Chrome浏览器加速网页加载和交互
传动链条材质常用有哪几种?都有什么特点?
人工智能如何加密码锁
配偶责任认定是什么?一文详解其定义、适用场景及认定方法
天灸——日光浴的好处与禁忌
小红书粉丝增长策略:从内容到互动的全方位解析
购买美版无锁手机:四大关键注意事项
如何在指定数据库中自动运行SQL脚本
中原王朝VS古越南:败多胜少?古代越南为何战斗力爆表?
中国传统建筑中的风水哲学
Win11迎史诗级更新!截图工具终于支持修改自动保存位置
项目进度落后如何应对
数据库字段加密:原理、实践与未来趋势
视频里的 AI 配音:引领未来的音频创新
敏使朗和西比灵的区别
五行⼋字喜用神:真的能准确揭示你的命运轨迹吗?
“低空经济+物流”如何布局?——从法律视角展开(上)