电商项目业务逻辑-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库存的问题
订单的流程
posted on 2018-09-21 15:56 companion 阅读(2692) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)