【DailyFresh】课程记录6---订单模块(订单并发)

if int(count) > sku.stock:

return JsonResponse({‘res’: 6, ‘errmsg’: ‘商品库存不足’})

为了演示效果,进行休眠操作,休眠10s:

for sku in skus:

import time

time.sleep(10)

TODO: 判断商品的库存

if int(count) > sku.stock:

return JsonResponse({‘res’: 6, ‘errmsg’: ‘商品库存不足’})

todo: 向df_order_goods表中添加一条记录

以鸡腿为例,其id=17,在页面生成订单,在数据库清除库存模拟这个过程

update df_goods_sku set stock=0 where id=17;

此时,订单信息表中会生成一条信息,实际上这一条信息也不应该有

解决上述问题,引入MySQL事务

MySQL事务: 一组sql操作,要么都成功,要么都失败

创建订单这一系列的操作应该放在一个事务中,要么全成功,要么全失败

事务的概念:一组sql语句,要么执行,要么全部不执行

事务的特点:

1.原子性:一组sql语句,要么执行,要么全部不执行

2.稳定性:有非法数据(外键约束之类),事务撤回。《—在事务中执行一些语句的时候,如果有非法数据,比如外键约束,导致执行失败,该事务也会被撤回。

3.隔离性:事务独立运行,一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。

4.可靠性:软硬件崩溃之后,数据库的引擎表InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得。所谓的可靠性就是你执行事务的时候,他会把事务保存到日志中去,如果软硬件崩溃之后,他会在你恢复之后根据日志的记录对它进行重新执行,保证它的可靠性。

MySQL中事务控制语句

你如果需要把一些语句放在事务中时,需要自己开启一个事务

使用BEGIN和START TRANSACTION:显式地开启一个事务;

开启事务之后,在BEGIN后的语句都在事务里面。

事务最终执行的结果有两个,一个是COMMIT,一个是ROLLBACK

COMMIT: 也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;

ROLLBACK:也可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

在数据库中进行测试BEGIN…COMMIT或BEGIN…ROLLBACK

在事务中还可以创建事务的保存点,在回滚的时候可以不把整个事务回滚,只是回滚到某一个保存点,标记点

SAVEPOINT identifier: SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有很多个SAVEPOINT;

RELEASE SAVEPOINT identifier:删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

ROLLBACK TO identifier:把事务回滚到标记点;

例子:

BEGIN;

SAVEPOINT s1;第一个保存点s1

…执行操作1修改数据库

SAVEPOINT s2;第二个保存点s2

…执行操作2修改数据库

ROLLBACK TO s2;

此时s2之前的操作都在

COMMIT;《----s2之前的操作永久生效

ROLLBACK;<—所有操作失效

P85 订单生成——django中使用事务

项目中刚才的错误:创建订单失败,但是依旧往订单信息表中添加了一条记录,但是理论上是不应该添加的,则需要把对数据库的一系列操作放入到事务中。

在项目中如何使用事务《—参考资料:

django文档–》Model 模型层—》高级—》事务【Django中使用MySQL的事务】

如何把django语句中的操作放入到事务中—》atomic

from django.db import transaction

要把一个函数中的sql语句放入一个事务中,只需要用@transaction.atomic装饰器装饰你的函数,他就会将你函数中涉及到数据库的操作放入一个事务里面

class OrderCommitView1(View):

“”“订单创建”“”

@transaction.atomic

def post(self, request):

结合atomic来说一下保存点的使用,假如说,对数据库的操作分为两段,没有必要进行完全回滚,就可以在某个位置设置一个保存点,比如在TODO:创建订单核心业务往下为一整个流程

# 在django中设置保存点

django.db.transaction

savepoint<—创建一个新的保存点

savepoint_commit<—

savepoint_rollback

在 【# todo: 保存订单信息表: 向df_order_info表中添加一条记录】之前设置一个保存点

# 设置事务保存点

save_id = transaction.savepoint()

在商品信息不存在这里,事务进行回滚

transaction.savepoint_rollback(save_id)

return JsonResponse({‘res’: 4, ‘errmsg’: ‘商品信息不存在’})

在商品库存不足处,也要进行事务回滚

将整个涉及数据库操作放入try…except中,如果发生异常都需要回滚到该位置

# 设置事务保存点

save_id = transaction.savepoint()

try:

order.save()

except Exception as e:

transaction.savepoint_rollback(save_id)

return JsonResponse({‘res’: 7, ‘errmsg’: ‘下单失败’})

# 没有问题的话则进行事务提交,从save_id到这里的所有数据库操作进行提交

transaction.savepoint_commit(save_id)

P89 订单生成——订单并发问题

订单并发的控制—见图

P90 订单并发——悲观锁

解决高并发问题的两个方案:

1.悲观锁

2.乐观锁

悲观锁对应进程锁的概念,进程操作一个公用的资源的时候,为了防止资源的一些问题,规定去拿锁,谁拿到锁,就可以修改该公用资源,拿不锁的则进行等待。只有锁被释放之后,其他进程才可以进行获取锁并修改资源。

在修改商品库存的时候,我认为别人也想改他,所以我们的规则是:在其中查询商品时,需要加一个锁,拿到【查询商品信息】这一行时,进行锁定,其余进程过来的时候也需要遵循该规则,在查询商品时,需要拿到一个锁,如果没有拿到锁则进行等待。

查询数据库信息并加锁:

select * from df_goods_sku where id=17 for update;

for update<—在获取数据的同时要拿到锁,拿到锁之后,代码才能继续往下执行。

当用户2也执行到这里时也需要去拿到锁,当拿不到锁的时候则进行等待。

此时用户1的代码继续往下走,当走完之后将库存进行更新。

执行完了 事务结束之后,锁会被释放

此时用户2的代码由阻塞状态进入运行状态

此时查询商品信息已经是用户1更新过库存之后的信息,此时库存不足,用户就不会下单。

在django中 获取商品的信息时

普通查询:

sku = GoodsSKU.objects.get(id=sku_id)

加入悲观锁查询:

# select * from df_goods_sku where id=17 for update;

sku = GoodsSKU.objects.select_for_update().get(id=sku_id)

测试:

在【从redis中获取用户所要购买的商品数量】之前

print(‘user:%d  stock:%d’ % (user.id, sku.stock))

# 打印完之后进行休眠

import time

time.sleep(10)

# 将鸡腿库存置位1

update df_goods_sku set stock=1 where id=17;

同时使用两个用户进行测试

通过悲观锁解决了并发的问题。

截图

P91 订单并发——乐观锁

乐观锁在查询数据的时候不加锁,在查询数据的时候不认为会有其他用户抢夺资源,不加锁,在更新的时候需要进行判断,判断更新时的库存和之前查出的库存是否一致

查询时库存为1

更新时,加and stock=1 进行库存校验

update df_gods_sku set stock=0, sales=1 where id=17 and stock=1;

如果更新成功说明没有人对库存做修改,如果更新失败则说明有人对库存进行了修改。

这就是乐观锁《----查找的时候不加锁,,更新的时候加一个判断。如果在更新的时候别人修改了库存,那么我的更新操作会失败

乐观锁在项目中的使用:

在django中 获取商品的信息时

使用普通查询:

sku = GoodsSKU.objects.get(id=sku_id)

在更新的时候:

# 修改商品表中的数据(更新库存和销量)

orgin_stock = sku.stock

new_stock = orgin_stock - int(count)

new_sales = sku.sales + int(count)

# 乐观锁

# update df_goods_sku set stock=new_stock, sales = new_sales where id=sku.id and stock=origin_stock

# res 返回的是一个数字,返回受影响的行数,你根据这个条件查出来之后进行更新,更新的几行会把最终结果进行返回

# 在这条代码中根据条件id=sku.id, stock=origin_stock进行查询,出来的要么是1要么是0

# 如果返回为0代表更新是失败的

res = GoodsSKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)

# 如果更新失败,回滚到save_id保存点

if res == 0:

transaction.savepoint_rollback(save_id)

return JsonResponse({‘res’: 7, ‘errmsg’: ''下单失败2})

测试:

在乐观锁更新之前

# print(‘user:%d  time:%d stock:%d’ % (user.id, i, sku.stock))

# import time

# time.sleep(10)

# 将鸡腿库存置位1

update df_goods_sku set stock=1 where id=17;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对以上面试题,小编已经把面试题+答案整理好了

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

面试专题

image

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

image

image

image
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
70318826)]

[外链图片转存中…(img-LrqHVVzr-1712070318826)]

[外链图片转存中…(img-Oz0f8vM3-1712070318827)]

面试专题

[外链图片转存中…(img-oHOFvcGj-1712070318827)]

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

[外链图片转存中…(img-OFU29mWx-1712070318827)]

[外链图片转存中…(img-44qqytQA-1712070318827)]

[外链图片转存中…(img-kmo5xBQc-1712070318827)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值