1. 程式人生 > 其它 >spring事務和加可重入鎖解決庫存超賣遇到的問題

spring事務和加可重入鎖解決庫存超賣遇到的問題

技術標籤:JAVA

想法:使用可重入鎖實現預鎖庫存,防止超賣現象,資料庫中存入100件商品,測試時每次購買一件,預鎖定一件。理想狀態是無超賣現象。

@PostMapping("/buyLock")
    @Transactional(rollbackFor = Exception.class,isolation = SERIALIZABLE)
    public String buyLock(Integer quantity,Long skuId){
        lock.lock();
        try {
            PmsSkuStock skuStock = skuStockMapper.selectByPrimaryKey(skuId);
            int left = skuStock.getStock() - skuStock.getLockStock();
            if (left < quantity){
                return "庫存不足";
            }
            int s = skuStockMapper.updateLockStock(skuId,quantity);
            if (s == 1){
                return "購買成功";
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return "購買失敗";
    }

結果發生了超賣,實際鎖定了101件。

解決方法一:去掉@Transactional註解,再試,沒有超賣了

@PostMapping("/buyLock")
    public String buyLock(Integer quantity,Long skuId){
        lock.lock();
        try {
            PmsSkuStock skuStock = skuStockMapper.selectByPrimaryKey(skuId);
            int left = skuStock.getStock() - skuStock.getLockStock();
            if (left < quantity){
                return "庫存不足";
            }
            int s = skuStockMapper.updateLockStock(skuId,quantity);
            if (s == 1){
                return "購買成功";
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return "購買失敗";
    }

解決方法二:加上@Transactional註解,將鎖提到事務外面,沒有超賣

@PostMapping("/buyLock")
    public String buyLock(Integer quantity,Long skuId){
        lock.lock();
        try {
            return buyLock1(quantity,skuId);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return "購買失敗";
    }

    @Transactional(rollbackFor = Exception.class)
    public String buyLock1(Integer quantity,Long skuId){
        PmsSkuStock skuStock = skuStockMapper.selectByPrimaryKey(skuId);
        int left = skuStock.getStock() - skuStock.getLockStock();
        if (left < quantity){
            return "庫存不足";
        }
        int s = skuStockMapper.updateLockStock(skuId,quantity);
        if (s == 1){
            return "購買成功";
        }
        return "購買失敗";
    }

解決方法三:spring事務預設使用mysql預設的事務隔離級別,是可重複讀,將spring事務級別設定成序列化,沒有超賣

@PostMapping("/buyLock")
    @Transactional(rollbackFor = Exception.class,isolation = SERIALIZABLE)
    public String buyLock(Integer quantity,Long skuId){
        lock.lock();
        try {
            PmsSkuStock skuStock = skuStockMapper.selectByPrimaryKey(skuId);
            int left = skuStock.getStock() - skuStock.getLockStock();
            if (left < quantity){
                return "庫存不足";
            }
            int s = skuStockMapper.updateLockStock(skuId,quantity);
            if (s == 1){
                return "購買成功";
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return "購買失敗";
    }

結論:

發生上述現象的原因是,事物未提交之前,鎖已經釋放(事物提交是在整個方法執行完),導致另一個事物讀取到了這個事物未提交的資料