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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
针对以上面试题,小编已经把面试题+答案整理好了
面试专题
除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
70318826)]
[外链图片转存中…(img-LrqHVVzr-1712070318826)]
[外链图片转存中…(img-Oz0f8vM3-1712070318827)]
面试专题
[外链图片转存中…(img-oHOFvcGj-1712070318827)]
除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习
[外链图片转存中…(img-OFU29mWx-1712070318827)]
[外链图片转存中…(img-44qqytQA-1712070318827)]
[外链图片转存中…(img-kmo5xBQc-1712070318827)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!