電商項目業務邏輯-3 訂單管理悲觀鎖和樂觀鎖


訂單管理是電商項目中的重點業務邏輯:

 1.訂單表

order_id 訂單主鍵

username

order_num 訂單編號

payment 支付方式

pay_platform

delivery 送貨方式

is_confirm 送貨前確認電話

order_sum

ship_fee   是否付款

order_state

payment_cash 貨到付款方式

distri_id 配送商id

delivery_method 送貨方式

payment_no 支付號

order_time 下單時間

pay_time 付款時間

deposit_time 到賬時間

success_time  成功時間

update_time  最后修改時間 例如地址寫錯修改之類的

srv_type 業務類型 0 無業務 1 2 需要辦理CRM業務

is_deleted 刪除

is_call 是否外呼過  催付款  0 未外呼 1 已外呼

delivery_no 物流編號

is_plant 發票是否打印

收貨地址等信息字段

2.訂單明細

order_DETAIL_ID

order_id

item_id 商品主鍵

item_name

item_no 商品編號

sku_id

sku_spec 規格值

market_price

sku_price

quantity  購買數量

活動之類的信息

活動營銷之類的

3.存在的問題:

(3.1)並發問題

如果客戶A和客戶B同時購買某一個商品的同一個skuid,A購買2個,B購買3個,如果庫存是100

<1>EbSku sku = skuDao.getSkuById();

<2>sku.setStockInventory(sku.getStockInventory()-detail.getQuantity());

<3>skuDao.update(sku);

  skuid           stock

  1001            100

A  1001(2)           98

B  1001(3)           97

假如A和B同時執行第1行代碼,A和B查詢上來的數據完全相同,在相同的數據基礎上來修改一定會產生並發的問題。

如果<1>加鎖那么AB查出來是不同的,也就不會產生並發問題。

如果查詢的時候使用 select * from sku where sku_id = ?  for update 加上for update的話會開啟事務,事務掛起,如果同時另一個窗口查詢同樣的sql,需要等到第一條sql提交之后才可以;所以加for update 可以解決加鎖的問題,這就是herbnate的悲觀鎖。

解決辦法:

1.)可使用數據庫的悲觀鎖,在第<1>行的代碼的查詢上加上for update會開啟事務,由於spring的傳播特性,每一個提交訂單的操作都是用的一個事務,如果A和B對同一條數據操作,當執行根據skuid查詢sku對象的時候一定有一個人被阻塞在外,直到update代碼執行完畢另一個才能得到執行,這樣可以保證我們的數據安全。但是帶來的問題就是性能低,對於互聯網項目要求性能高的此種方式不適合。

2.)樂觀鎖:version控制並發的字段

    skuid       stock        version

默認值  1001              100                1

A(2)        1001            

B(3)

update所對應的sql

A:

update sku s set s.stock = 98 ,t.version = s.version+1 where sku_id = 1001 and t.version = 1

and t.stock >2

B;

update sku s set s.stock = 97 ,t.version = s.version+1 where sku_id = 1001 and t.version = 1

and t.stock >3

以上AB同時執行的時候,修改庫存的時候由於查詢上來的數據完全相同所以執行的sql傳遞過來的數據也相同,A和B誰先執行有隨機性,假設A先執行由於A和B對同一條數據在修改,那么B一定被阻塞在外,A提交事務后B才能得到執行,B的update的相應條數是0,相當於沒有得到執行,那么B執行的條件需要做更改:

導致相應條數是0有2個原因:

1>version 並發問題導致 

2>stock導致的 可能庫存不夠了

update sku s set s.stock = 97 ,t.version = s.version+1 where sku_id = 1001 and t.version = 1

and t.stock >2

目前采用先查詢后修改的方式:

public void saveOrder(EbOrder order,List<EbOrderDetail> detailList){

  orderDao.save(order);

  for(EbOrderDetail detail : detailList){]

    detail.setOrderId(order.getOrderId());

    orderDetailDao.saveOrderDetail(detail);

    //扣減庫存

    EbSku sku = skuDao.getSkuById(detail.getSkuId());

    sku.setStockInventory(sku.getStockInventory()-detail.getQuantity());

    int flag = skuDao.update(sku);

    if(flag == 0){

      //一旦出現flag為0,那么就是出問題了,那么這個訂單不能提交了,訂單不讓入庫了,那么當前整個事務回滾;如何實現呢?拋出運行時異常,那么整個事務就回滾了;

    //問題來了 拋出異常如何查看是version還是stock的問題?

      EbSku sku = skuDao.getSkuById(detail.getSkuId());

      if(sku.getStock() < detail.getQuantity()){

        throw new 自定義異常;

      }else{

        //並發問題引起的

        throw new 並發引起的自定義異常

      }

    }

    //redis不受事務控制 對redis中的數據進行更新處理

  }

}

在外層調用saveOrder()處

try{

  orderService.saveOrder(order,detailList)

}catch(Exceptione){

  if(e instanceof EbStockException){

    model.addAttribute("tip","stock error")

  }else if(e instanceof 自定義的版本號異常){

    orderService.saveOrder(order,detailList);//當版本號問題的時候重新提交就可以了,但是如果重新提交再次發生並發那依然還是上次分析流程

  }

}

分析:

其實在上述代碼中可以不查詢直接修改,但是在一些比較復雜的項目中,有些數據必須先查詢出來處理其他業務,本項目中可以直接進行修改

如果業務不是必須要查詢再修改就盡量不要先查詢后修改

skuDao.update(sku);

update sku t set t.stock = t.stock - #{quantity} where t.sku_id = #{skuId} and t.stock >= #{quantity}

此種情況一旦出現上述flag ==0 的情況 那一定是stock庫存的問題

 

訂單的流程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM