訂單管理是電商項目中的重點業務邏輯: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庫存的問題
訂單的流程