基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见
订单并发这个问题我想大家都是有一定认识的,这里我说一下我的一些浅见,我会尽可能的让大家了解如何解决这类问题。
在解释如何解决订单并发问题之前,需要先了解一下什么是数据库的事务。(我用的是mysql数据库,这里以mysql为例)
1) 事务概念
一组mysql语句,要么执行,要么全不不执行。
2) mysql事务隔离级别
Read Committed(读取提交内容)
如果是Django2.0以下的版本,需要去修改到这个隔离级别,不然乐观锁操作时无法读取已经被修改的数据
RepeatableRead(可重读)
这是这是Mysql默认的隔离级别,可以到mysql的配置文件中去修改;
transcation-isolation = READ-COMMITTED
在mysql配置文件中添加这行然后重启mysql就可以将事务隔离级别修改至Read Committed
其他事务知识这里不会用到就不浪费时间去做介绍了。
悲观锁:开启事务,然后给mysql的查询语句最后加上for update。
这是在干什么呢。可能大家有些不理解,其实就是给资源加上和多线程中加互斥锁一样的东西,确保在一个事务结束之前,别的事务无法对该数据进行操作。
下面是悲观锁的代码,加锁和解锁都是需要消耗CPU资源的,所以在订单并发少的情况使用乐观锁会是一个更好的选择。
class OrderCommitView(View):
"""悲观锁"""
# 开启事务装饰器
@transaction.atomic
def post(self,request):
"""订单并发 ———— 悲观锁"""
# 拿到商品id
goods_ids = request.POST.getlist('goods_ids')
# 校验参数
if len(goods_ids) == 0 :
return JsonResponse({'res':0,'errmsg':'数据不完整'})
# 当前时间字符串
now_str = datetime.now().strftime('%Y%m%d%H%M%S')
# 订单编号
order_id = now_str + str(request.user.id)
# 地址
pay_method = request.POST.get('pay_method')
# 支付方式
address_id = request.POST.get('address_id')
try:
address = Address.objects.get(id=address_id)
except Address.DoesNotExist:
return JsonResponse({'res':1,'errmsg':'地址错误'})
# 商品数量
total_count = 0
# 商品总价
total_amount = 0
# 获取redis连接
conn = get_redis_connection('default')
# 拼接key
cart_key = 'cart_%d' % request.user.id
#
# 创建保存点
sid = transaction.savepoint()
order_info = OrderInfo.objects.create(
order_id = order_id,
user = request.user,
addr = address,
pay_method = pay_method,
total_count = total_count,
total_price = total_amount
)
for goods_id in goods_ids:
# 尝试查询商品
# 此处考虑订单并发问题,
try:
# goods = Goods.objects.get(id=goods_id) # 不加锁查询
goods = Goods.objects.select_for_update().get(id=goods_id) # 加互斥锁查询
except Goodsgoods.DoesNotExist:
# 回滚到保存点
transaction.rollback(sid)
return JsonResponse({'res':2,'errmsg':'商品信息错误'})
# 取出商品数量
count = conn.hget(cart_key,goods_id)
if count is None:
# 回滚到保存点
transaction.rollback(sid)
return JsonResponse({'res':3,'errmsg':'商品不在购物车中'})
count = int(count)
if goods.stock < count:
# 回滚到保存点
transaction.rollback(sid)
return JsonResponse({'res':4,'errmsg':'库存不足'})
# 商品销量增加
goods.sales += count
# 商品库存减少
goods.stock -= count
# 保存到数据库
goods.save()
OrderGoods.objects.create(
order = order_info,
goods = goods,
count = count,
price = goods.price
)
# 累加商品件数
total_count += count
# 累加商品总价
total_amount += (goods.price) * count
# 更新订单信息中的商品总件数
order_info.total_count = total_count
# 更新订单信息中的总价格
order_info.total_price = total_amount + order_info.transit_price
order_info.save()
# 事务提交
transaction.commit()
return JsonResponse({'res':5,'errmsg':'订单创建成功'})
然后就是乐观锁查询了,相比悲观锁,乐观锁其实并不能称为是锁,那么它是在做什么事情呢。
其实是在你要进行数据库操作时先去查询一次数据库中商品的库存,然后在你要更新数据库中商品库存时,将你一开始查询到的库存数量和商品的ID一起作为更新的条件,当受影响行数返回为0时,说明没有修改成功,那么就是说别的进程修改了该数据,那么你就可以回滚到之前没有进行数据库操作的时候,重新查询,重复之前的操作一定次数,如果超过你设置的次数还是不能修改那么就直接返回错误结果。
该方法只适用于订单并发较少的情况,如果失败次数过多,会带给用户不良体验,同时适用该方法要注意数据库的隔离级别一定要设置为Read Committed 。
最好在使用乐观锁之前查看一下数据库的隔离级别,mysql中查看事物隔离级别的命令为
select @@global.tx_isolation;
乐观锁代码
class OrderCommitView(View):
"""乐观锁"""
# 开启事务装饰器
@transaction.atomic
def post(self,request):
"""订单并发 ———— 乐观锁"""
# 拿到id
goods_ids = request.POST.get('goods_ids')
if len(goods_ids) == 0 :
return JsonResponse({'res':0,'errmsg':'数据不完整'})
# 当前时间字符串
now_str = datetime.now().strftime('%Y%m%d%H%M%S')
# 订单编号
order_id = now_str + str(request.user.id)
# 地址
pay_method = request.POST.get('pay_method')
# 支付方式
address_id = request.POST.get('address_id')
try:
address = Address.objects.get(id=address_id)
except Address.DoesNotExist:
return JsonResponse({'res':1,'errmsg':'地址错误'})
# 商品数量
total_count = 0
# 商品总价
total_amount = 0
# 订单运费
transit_price = 10
# 创建保存点
sid = transaction.savepoint()
order_info = OrderInfo.objects.create(
order_id = order_id,
user = request.user,
addr = address,
pay_method = pay_method,
total_count = total_count,
total_price = total_amount,
transit_price = transit_price
)
# 获取redis连接
goods = get_redis_goodsection('default')
# 拼接key
cart_key = 'cart_%d' % request.user.id
for goods_id in goods_ids:
# 尝试查询商品
# 此处考虑订单并发问题,
# redis中取出商品数量
count = goods.hget(cart_key, goods_id)
if count is None:
# 回滚到保存点
transaction.savepoint_rollback(sid)
return JsonResponse({'res': 3, 'errmsg': '商品不在购物车中'})
count = int(count)
for i in range(3):
# 若存在订单并发则尝试下单三次
try:
goods = Goodsgoods.objects.get(id=goods_id) # 不加锁查询
# goods = Goodsgoods.objects.select_for_update().get(id=goods_id) # 加互斥锁查询
except Goodsgoods.DoesNotExist:
# 回滚到保存点
transaction.savepoint_rollback(sid)
return JsonResponse({'res':2,'errmsg':'商品信息错误'})
origin_stock = goods.stock
print(origin_stock, 'stock')
print(goods.id, 'id')
if origin_stock < count:
# 回滚到保存点
transaction.savepoint_rollback(sid)
return JsonResponse({'res':4,'errmsg':'库存不足'})
# # 商品销量增加
# goods.sales += count
# # 商品库存减少
# goods.stock -= count
# # 保存到数据库
# goods.save()
# 如果下单成功后的库存
new_stock = goods.stock - count
new_sales = goods.sales + count
res = Goodsgoods.objects.filter(stock=origin_stock,id=goods_id).update(stock=new_stock,sales=new_sales)
print(res)
if res == 0:
if i == 2:
# 回滚
transaction.savepoint_rollback(sid)
return JsonResponse({'res':5,'errmsg':'下单失败'})
continue
else:
break
OrderGoods.objects.create(
order = order_info,
goods = goods,
count = count,
price = goods.price
)
# 删除购物车中记录
goods.hdel(cart_key,goods_id)
# 累加商品件数
total_count += count
# 累加商品总价
total_amount += (goods.price) * count
# 更新订单信息中的商品总件数
order_info.total_count = total_count
# 更新订单信息中的总价格
order_info.total_price = total_amount + order_info.transit_price
order_info.save()
# 事务提交
transaction.savepoint_commit(sid)
return JsonResponse({'res':6,'errmsg':'订单创建成功'})
本文来自:
https://blog.csdn.net/qq_36012543/article/details/79679690?utm_source=copy
-END-
识别图中二维码,领取python全套视频资料
基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见的更多相关文章
- Hibernate事务与并发问题处理(乐观锁与悲观锁)
目录 一.数据库事务的定义 二.数据库事务并发可能带来的问题 三.数据库事务隔离级别 四.使用Hibernate设置数据库隔离级别 五.使用悲观锁解决事务并发问题 六.使用乐观锁解决事务并发问题 Hi ...
- mysql的锁--行锁,表锁,乐观锁,悲观锁
一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...
- 【数据库】mysql深入理解乐观锁与悲观锁
转载:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时 ...
- [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析
前言: 在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念.数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务 ...
- 乐观锁vs悲观锁
引言 为什么需要锁(并发控制) 在并发的环境中,会存在多个用户同时更新同一条数据,这时就会产生冲突. 冲突结果: 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失. 脏读:当一个事 ...
- 多线程深入:乐观锁与悲观锁以及乐观锁的一种实现方式-CAS(转)
原文:https://www.cnblogs.com/qjjazry/p/6581568.html 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 ...
- Mysql乐观锁与悲观锁
乐观锁和悲观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常重要的基础理念. Mysql的悲观锁 什么是悲观锁(Pessimistic Lock): 悲观锁的特点是先获取锁,再进行业务操作,即 ...
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...
- 乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...
随机推荐
- android圆形图像
在网上找了一下,最简单的是利用canvas的setXfermode,来控制图片重合部分的显示策略. 图片混合时,先画的是dst,后画的是src,各种混合的方式如下,其中圆形是dst,正方形是src: ...
- linux进程同步机制_转
转自:Linux进程同步机制 具体应用可参考:线程同步 IPC之信号量 为了能够有效的控制多个进程之间的沟通过程,保证沟通过程的有序和和谐,OS必须提供一 定的同步机制保证进程之间不会自说 ...
- Android基础总结(四)网络通信
网络图片查看器 确定图片的网址 发送http请求 URL url = new URL(address); //获取连接对象,并没有建立连接 HttpURLConnection conn = (Http ...
- window.parent.document解决原生js或jQuery 实现父窗口的问题
做WEB前端开发的过程中,经常会有这样的需求,用户点击[编辑]按钮,弹出一个对话框,在里边修改相应的值,然后把修改后的值显示在原页面,最后点击保存. 用window.parent.document.g ...
- MapReduce 中的两表 join 几种方案简介
转自:http://my.oschina.net/leejun2005/blog/95186 MapSideJoin例子:http://my.oschina.net/leejun2005/blog/1 ...
- DTD与XML Schema都是XML文档。(选择1项)
DTD与XML Schema都是XML文档.(选择1项) A.正确 B.不正确 解答:DTD不是XML文件, schema是XML文档
- 基于WebSocket实现网页版聊天室
WebSocket ,HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,其使用简单,应用场景也广泛,不同开发语言都用种类繁多的实现,仅Java体系中,Tomcat,Jetty,Sp ...
- Linux下Vi/Vim的使用方法
本文介绍了vi (vim)的基本使用方法,但对于普通用户来说基本上够了!i/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim 是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特 ...
- open() 函数以 r+ 模式打开文件
第一种用法:如果我们对文件进行写操作,则默认会从第一行开始写,且直接覆盖第一行的内容 [root@localhost ~]$ cat 1.txt # 文件内容如下 aaa bbb ccc In [1] ...
- IOS7开发~新UI学起(三)
1.UITextView: A ) IOS7新增加的 UITextViewDelegate 方法: - (BOOL)textView:(UITextView *)textView shoul ...