实战:使用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乐观锁解决超卖问题
商品超买超卖问题分析及实战
热门推荐
命理学不是迷信!现代职业规划中的实用智慧
红木香耐寒多少度?详解红木香越冬养护要点
红木香冬季移栽能成活吗?这些关键措施助你成功
从经典到现代:戏剧中角色性格变化的艺术表现
企业应用程序架构:概念、类型与最佳实践
现代伊兰特车主分享:20万公里保养秘籍
证据获取是什么?法律程序中的关键环节详解
管理的社会需求:从资源配置到组织竞争力
说服人的5个黄金法则
EPS模块:农村自建房的新宠儿!
薛定谔的猫:20世纪初的科学奇想
一招教你告别黑头:有效去除与预防秘籍
双十一外卖配送效率大揭秘:AI派单与无人配送双轮驱动
中医食疗:芹菜炒香菇对抗脂肪肝
一行一局一会组合拳助力A股企稳回升
AI技术如何重塑未来社会?
如何建立Excel模板并使用
无论是否选择花钱报班,自学能力是获取其他各种能力的“元能力”
江西上高:紫薇花开石头坑 空心村变成新农村
小朋友咳嗽厉害怎样快速止咳
心理暗示在弱视治疗中的作用与局限
通过《绝美之城》学地道意语
职场新人如何快速提升工作效率?
十神生克关系表 十神生克关系快速记忆法
最新网络安全威胁:如何用多层次防御保护你的数据?
拆除施工中的环境保护与生态修复
Wi-Fi 6E:更快、更稳的下一代无线网络技术
ATS简历优化技巧:避免常见错误,确保通过筛选
如何处理友情中的背叛与疏远
家校合作:提升孩子自信的关键