日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

mysql乐观锁处理超卖_通过乐观锁解决库存超卖的问题

發布時間:2023/11/27 生活经验 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql乐观锁处理超卖_通过乐观锁解决库存超卖的问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

在通過多線程來解決高并發的問題上,線程安全往往是最先需要考慮的問題,其次才是性能。庫存超賣問題是有很多種技術解決方案的,比如悲觀鎖,分布式鎖,樂觀鎖,隊列串行化,Redis原子操作等。本篇通過MySQL樂觀鎖來演示基本實現。

開發前準備

1. 環境參數

開發工具:IDEA

基礎工具:Maven+JDK8

所用技術:SpringBoot+Mybatis

數據庫:MySQL5.7

SpringBoot版本:2.2.5.RELEASE

2. 創建數據庫

基本的scheme已建好,演示就拿最簡單的數據結構最好不過了。

DROP TABLE IF EXISTS `goods`;

CREATE TABLE `goods` (

`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品id',

`name` varchar(30) DEFAULT NULL COMMENT '商品名稱',

`stock` int(11) DEFAULT '0' COMMENT '商品庫存',

`version` int(11) DEFAULT '0' COMMENT '并發版本控制',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '商品表';

INSERT INTO `goods` VALUES (1, 'iphone', 10, 0);

INSERT INTO `goods` VALUES (2, 'huawei', 10, 0);

DROP TABLE IF EXISTS `order`;

CREATE TABLE `order` (

`id` int(11) AUTO_INCREMENT,

`uid` int(11) COMMENT '用戶id',

`gid` int(11) COMMENT '商品id',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '訂單表';

沒有環境的小伙伴可以通過Docker實戰之MySQL主從復制,快速的進行MySQL環境的搭建。創建數據庫test,然后導入相關的sql初始化Table。

3. 配置 pom 文件中的相關依賴

下邊是pom.xml依賴配置。

org.springframework.boot

spring-boot-starter-web

org.mybatis.spring.boot

mybatis-spring-boot-starter

2.1.1

org.springframework.boot

spring-boot-devtools

runtime

true

mysql

mysql-connector-java

runtime

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

4. 配置 application.yml

由于演示中MyBatis基于接口映射,配置簡單。application.yml中只需要配置mysql相關即可

spring:

datasource:

type: com.zaxxer.hikari.HikariDataSource

driverClassName: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3307/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC

username: root

password: root

5. 創建相關Bean

package com.idcmind.ants.entity;

public class Goods {

private int id;

private String name;

private int stock;

private int version;

...

此處省略getter、setter以及 toString方法

}

public class Order {

private int id;

private int uid;

private int gid;

...

此處省略getter、setter以及 toString方法

}

樂觀鎖解決庫存超賣方案

1. Dao層開發

GoodsDao.java

@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);

}

OrderDao.java

這里需要特別注意,由于order是sql中的關鍵字,所以表名需要加上反引號。

@Mapper

public interface OrderDao {

/**

* 插入訂單

* 注意: order表是關鍵字,需要`order`

* @param order

*/

@Insert("INSERT INTO `order` (uid, gid) VALUES (#{uid}, #{gid})")

@Options(useGeneratedKeys = true, keyProperty = "id")

int insertOrder(Order order);

}

2. Service層開發

GoodsService.java

@Service

public class GoodsService {

@Autowired

private GoodsDao goodsDao;

@Autowired

private OrderDao orderDao;

/**

* 扣減庫存

* @param gid 商品id

* @param uid 用戶id

* @return SUCCESS 1 FAILURE 0

*/

@Transactional

public int sellGoods(int gid, int uid) {

// 獲取庫存

Goods goods = goodsDao.getStock(gid);

if (goods.getStock() > 0) {

// 樂觀鎖更新庫存

int update = goodsDao.decreaseStockForVersion(gid, goods.getVersion());

// 更新失敗,說明其他線程已經修改過數據,本次扣減庫存失敗,可以重試一定次數或者返回

if (update == 0) {

return 0;

}

// 庫存扣減成功,生成訂單

Order order = new Order();

order.setUid(uid);

order.setGid(gid);

int result = orderDao.insertOrder(order);

return result;

}

// 失敗返回

return 0;

}

}

并發測試

這里我們寫個單元測試進行并發測試。

@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();

}

}

查看數據庫驗證是否超賣

上圖的結果與我們的預期一致。此外還可以通過Postman或者Jmeter進行并發測試。由于不是此處的重點,不再做演示,感興趣的小伙伴可以留言,我會整理下相關的教程。

后續

這篇文章通過數據庫樂觀鎖已經解決了庫存超賣的問題,不過效率上并不是最優方案,后續會完善其他方案的演示。文中如有錯漏之處,還望大家不吝賜教。

公眾號【當我遇上你】

總結

以上是生活随笔為你收集整理的mysql乐观锁处理超卖_通过乐观锁解决库存超卖的问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。