数据库中的超卖现象

# 改造代码 引入数据库信息

编写数据库库存信息

image-20221002211457237

编写实体类

@Data
@TableName("db_stock")
public class Stock {
    
    /**
     * 主键
     */
    private Long id;

    /**
     * 商品编号
     */
    private String productCode;

    /**
     * 仓库
     */
    private String warehouse;

    /**
     * 库存量
     */
    private Integer count;
    
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

使用MyBatis-Plus并且编写对应的Mapper、Service、Controller层

在业务层编写减库存逻辑:

    @Autowired
    private StockMapper stockMapper;

    public void deduct() {
//        lock.lock();
        try {
            QueryWrapper<Stock> stockQueryWrapper = new QueryWrapper<>();
            stockQueryWrapper.eq("product_code", "1001");
            Stock stock = stockMapper.selectOne(stockQueryWrapper);
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                stockMapper.updateById(stock);
            }
        } finally {
//            lock.unlock();
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

进行压力测试,结果为:

image-20221002212119989

此时我们发现当牵扯到数据库业务时,存在严重的并发问题

# 分析

理论上,库存剩余量为0到4950,因为在压力测试中,我设置了50个请求,每一个请求同时有100个线程并发进行,所以最坏的情况就是每一次请求只有一个请求减库存是正确的。

# 使用本地锁解决数据库超卖问题

我们使用之前的本地锁进行优化:

private ReentrantLock lock = new ReentrantLock();

    @Autowired
    private StockMapper stockMapper;

    public void deduct() {
        lock.lock();
        try {
            QueryWrapper<Stock> stockQueryWrapper = new QueryWrapper<>();
            stockQueryWrapper.eq("product_code", "1001");
            Stock stock = stockMapper.selectOne(stockQueryWrapper);
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                stockMapper.updateById(stock);
            }
        } finally {
            lock.unlock();
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

通过压力测试,我们可以顺利地解决并发问题

image-20221002212822672

另外,使用synchronized也是没有问题的。