基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见
訂單并發這個問題我想大家都是有一定認識的,這里我說一下我的一些淺見,我會盡可能的讓大家了解如何解決這類問題。
在解釋如何解決訂單并發問題之前,需要先了解一下什么是數據庫的事務。(我用的是mysql數據庫,這里以mysql為例)
一組mysql語句,要么執行,要么全不不執行。
Read Committed(讀取提交內容)
如果是Django2.0以下的版本,需要去修改到這個隔離級別,不然樂觀鎖操作時無法讀取已經被修改的數據
RepeatableRead(可重讀)
這是這是Mysql默認的隔離級別,可以到mysql的配置文件中去修改;
transcation-isolation = READ-COMMITTED在mysql配置文件中添加這行然后重啟mysql就可以將事務隔離級別修改至Read Committed
其他事務知識這里不會用到就不浪費時間去做介紹了。
悲觀鎖:開啟事務,然后給mysql的查詢語句最后加上for update。
這是在干什么呢。可能大家有些不理解,其實就是給資源加上和多線程中加互斥鎖一樣的東西,確保在一個事務結束之前,別的事務無法對該數據進行操作。
下面是悲觀鎖的代碼,加鎖和解鎖都是需要消耗CPU資源的,所以在訂單并發少的情況使用樂觀鎖會是一個更好的選擇。
''' 遇到問題沒人解答?小編創建了一個Python學習交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' class OrderCommitView(View):"""悲觀鎖"""# 開啟事務裝飾器@transaction.atomicdef post(self,request):"""訂單并發 ———— 悲觀鎖"""# 拿到商品idgoods_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')# 拼接keycart_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_priceorder_info.save()# 事務提交transaction.commit()return JsonResponse({'res':5,'errmsg':'訂單創建成功'})然后就是樂觀鎖查詢了,相比悲觀鎖,樂觀鎖其實并不能稱為是鎖,那么它是在做什么事情呢。
其實是在你要進行數據庫操作時先去查詢一次數據庫中商品的庫存,然后在你要更新數據庫中商品庫存時,將你一開始查詢到的庫存數量和商品的ID一起作為更新的條件,當受影響行數返回為0時,說明沒有修改成功,那么就是說別的進程修改了該數據,那么你就可以回滾到之前沒有進行數據庫操作的時候,重新查詢,重復之前的操作一定次數,如果超過你設置的次數還是不能修改那么就直接返回錯誤結果。
該方法只適用于訂單并發較少的情況,如果失敗次數過多,會帶給用戶不良體驗,同時適用該方法要注意數據庫的隔離級別一定要設置為Read Committed 。
最好在使用樂觀鎖之前查看一下數據庫的隔離級別,mysql中查看事物隔離級別的命令為
select @@global.tx_isolation;
樂觀鎖代碼
class OrderCommitView(View):"""樂觀鎖"""# 開啟事務裝飾器@transaction.atomicdef post(self,request):"""訂單并發 ———— 樂觀鎖"""# 拿到idgoods_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')# 拼接keycart_key = 'cart_%d' % request.user.idfor 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.stockprint(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 - countnew_sales = goods.sales + countres = 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':'下單失敗'})continueelse:breakOrderGoods.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_priceorder_info.save()# 事務提交transaction.savepoint_commit(sid)return JsonResponse({'res':6,'errmsg':'訂單創建成功'})總結
以上是生活随笔為你收集整理的基于Django的乐观锁与悲观锁解决订单并发问题的一点浅见的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单点登录的实现原理
- 下一篇: python3.6 使用pyinstal