沖突比較少的時候,使用樂觀鎖。
沖突比較多的時候,使用悲觀鎖。
(1) 悲觀鎖
select * from df_goods_sku where id=17 for update;
悲觀鎖獲取數據時對數據行了鎖定,其他事務要想獲取鎖,必須等原事務結束。
視圖函數views.py
from django.db import transaction # 事務處理 class OrderCommitView(View): '''訂單創建''' @transaction.atomic() # 事務處理裝飾器 def post(self, request): '''訂單創建''' # 判斷用戶是否登錄 user = request.user if not user.is_authenticated(): # 用戶未登錄 return JsonResponse({'res':0, 'errmsg':'用戶未登錄'}) # 接收參數 addr_id = request.POST.get('addr_id') pay_method = request.POST.get('pay_method') sku_ids = request.POST.get('sku_ids') # 1,3 # 校驗參數 if not all([addr_id, pay_method, sku_ids]): return JsonResponse({'res':1, 'errmsg':'參數不完整'}) # 校驗支付方式 if pay_method not in OrderInfo.PAY_METHODS.keys(): # 需要在orders/model.py中添加PAY_METHODS字典 return JsonResponse({'res':2, 'errmsg':'非法的支付方式'}) # 校驗地址 try: addr = Address.objects.get(id=addr_id) except Address.DoesNotExist: # 地址不存在 return JsonResponse({'res':3, 'errmsg':'地址非法'}) # todo: 創建訂單核心業務 # 組織參數 # 訂單id: 20171122181630+用戶id order_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id) # 運費 transit_price = 10 # 總數目和總金額 total_count = 0 total_price = 0 # 設置保存點 save_id = transaction.savepoint() try: # todo: 向df_order_info表中添加一條記錄 order = OrderInfo.objects.create(order_id=order_id, user=user, addr=addr, pay_method=pay_method, total_count=total_count, total_price=total_price, transit_price=transit_price) # todo: 用戶的訂單中有幾個商品,需要向df_order_goods表中加入幾條記錄 conn = get_redis_connection('default') cart_key = 'cart_%d'%user.id sku_ids = sku_ids.split(',') for sku_id in sku_ids: # 獲取商品的信息 try: # 悲觀鎖 # select * from df_goods_sku where id=sku_id for update; sku = GoodsSKU.objects.select_for_update().get(id=sku_id) except: # 商品不存在 transaction.savepoint_rollback(save_id) # 回滾到保存點 return JsonResponse({'res':4, 'errmsg':'商品不存在'}) # 查看誰拿到鎖 print('user:%d stock:%d'%(user.id,sku.stock)) # 從redis中獲取用戶所要購買的商品的數量 count = conn.hget(cart_key, sku_id) # todo: 判斷商品的庫存 if int(count) > sku.stock: transaction.savepoint_rollback(save_id) # 回滾到保存點 return JsonResponse({'res': 6, 'errmsg': '商品庫存不足'}) # todo: 向df_order_goods表中添加一條記錄 OrderGoods.objects.create(order=order, sku=sku, count=count, price=sku.price) import time time.sleep(10) # todo: 更新商品的庫存和銷量 sku.stock -= int(count) sku.sales += int(count) sku.save() # todo: 累加計算訂單商品的總數量和總價格 amount = sku.price*int(count) total_count += int(count) total_price += amount # todo: 更新訂單信息表中的商品的總數量和總價格 order.total_count = total_count order.total_price = total_price order.save() except Exception as e: transaction.savepoint_rollback(save_id) # 回滾到保存點 return JsonResponse({'res': 7, 'errmsg': '下單失敗'}) # 如果沒失敗則提交事務 transaction.savepoint_commit(save_id) # todo: 清除用戶購物車中對應的記錄 conn.hdel(cart_key, *sku_ids) # 返回應答 return JsonResponse({'res':5, 'message':'創建成功'})
(2) 樂觀鎖
查詢時不鎖數據,提交更改時進行判斷.
update df_goods_sku set stock=0, sales=1 where id=17 and stock=1;
使用樂觀鎖前,要先 設置mysql事務的隔離級別
打開mysql配置文件: sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
添加下面一行代碼
transaction-isolation = READ-COMMITTED
保存配置文件,重啟mysql服務。
sudo service mysql restart
實例views.py
class OrderCommitView(View): '''訂單創建''' @transaction.atomic() # 事務處理裝飾器 def post(self, request): '''訂單創建''' # 判斷用戶是否登錄 user = request.user if not user.is_authenticated(): # 用戶未登錄 return JsonResponse({'res':0, 'errmsg':'用戶未登錄'}) # 接收參數 addr_id = request.POST.get('addr_id') pay_method = request.POST.get('pay_method') sku_ids = request.POST.get('sku_ids') # 1,3 # 校驗參數 if not all([addr_id, pay_method, sku_ids]): return JsonResponse({'res':1, 'errmsg':'參數不完整'}) # 校驗支付方式 if pay_method not in OrderInfo.PAY_METHODS.keys(): # 需要在orders/model.py中添加PAY_METHODS字典 return JsonResponse({'res':2, 'errmsg':'非法的支付方式'}) # 校驗地址 try: addr = Address.objects.get(id=addr_id) except Address.DoesNotExist: # 地址不存在 return JsonResponse({'res':3, 'errmsg':'地址非法'}) # todo: 創建訂單核心業務 # 組織參數 # 訂單id: 20171122181630+用戶id order_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id) # 運費 transit_price = 10 # 總數目和總金額 total_count = 0 total_price = 0 # 設置保存點 save_id = transaction.savepoint() try: # todo: 向df_order_info表中添加一條記錄 order = OrderInfo.objects.create(order_id=order_id, user=user, addr=addr, pay_method=pay_method, total_count=total_count, total_price=total_price, transit_price=transit_price) # todo: 用戶的訂單中有幾個商品,需要向df_order_goods表中加入幾條記錄 conn = get_redis_connection('default') cart_key = 'cart_%d'%user.id sku_ids = sku_ids.split(',') for sku_id in sku_ids: for i in range(3): # 多次嘗試 # 獲取商品的信息 try: sku = GoodsSKU.objects.select_for_update().get(id=sku_id) except: # 商品不存在 transaction.savepoint_rollback(save_id) # 回滾到保存點 return JsonResponse({'res':4, 'errmsg':'商品不存在'}) # 查看誰拿到鎖 print('user:%d stock:%d'%(user.id,sku.stock)) # 從redis中獲取用戶所要購買的商品的數量 count = conn.hget(cart_key, sku_id) # todo: 判斷商品的庫存 if int(count) > sku.stock: transaction.savepoint_rollback(save_id) # 回滾到保存點 return JsonResponse({'res': 6, 'errmsg': '商品庫存不足'}) # todo: 更新商品的庫存和銷量 orgin_stock = sku.stock # 原有的庫存 new_stock = orgin_stock - int(count) # 要修改的庫存 new_sales = sku.sales + int(count) # 要修改的銷量 # 返回受影響行數 查詢是否成功 放在添加記錄前先檢測再添加 --樂觀鎖 res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock).update(stock=new_stock, sales=new_sales) if res == 0: if i == 2: # 嘗試的第3次,失敗了回滾 transaction.savepoint_rollback(save_id) return JsonResponse({'res': 7, 'errmsg': '下單失敗'}) continue # todo: 向df_order_goods表中添加一條記錄 OrderGoods.objects.create(order=order, sku=sku, count=count, price=sku.price) # todo: 累加計算訂單商品的總數量和總價格 amount = sku.price*int(count) total_count += int(count) total_price += amount # 跳出循環 break # todo: 更新訂單信息表中的商品的總數量和總價格 order.total_count = total_count order.total_price = total_price order.save() except Exception as e: transaction.savepoint_rollback(save_id) # 回滾到保存點 return JsonResponse({'res': 7, 'errmsg': '下單失敗7'}) # 如果沒失敗則提交事務 transaction.savepoint_commit(save_id) # todo: 清除用戶購物車中對應的記錄 conn.hdel(cart_key, *sku_ids) # 返回應答 return JsonResponse({'res':5, 'message':'創建成功'})