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

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

 1.訂單表spring

order_id 訂單主鍵sql

username數據庫

order_num 訂單編號安全

payment 支付方式併發

pay_platform性能

delivery 送貨方式ui

is_confirm 送貨前確認電話orm

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庫存的問題

 

訂單的流程

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息