訂單併發這個問題我想你們都是有必定認識的,這裏我說一下個人一些淺見,我會盡量的讓你們瞭解如何解決這類問題。python
在解釋如何解決訂單併發問題以前,須要先了解一下什麼是數據庫的事務。(我用的是mysql數據庫,這裏以mysql爲例)mysql
一組mysql語句,要麼執行,要麼全不不執行。redis
Read Committed(讀取提交內容)sql
若是是Django2.0如下的版本,須要去修改到這個隔離級別,否則樂觀鎖操做時沒法讀取已經被修改的數據數據庫
RepeatableRead(可重讀)多線程
這是這是Mysql默認的隔離級別,能夠到mysql的配置文件中去修改;併發
transcation-isolation = READ-COMMITTED
複製代碼
在mysql配置文件中添加這行而後重啓mysql就能夠將事務隔離級別修改至Read Committedpost
其餘事務知識這裏不會用到就不浪費時間去作介紹了。學習
悲觀鎖:開啓事務,而後給mysql的查詢語句最後加上for update。atom
這是在幹什麼呢。可能你們有些不理解,其實就是給資源加上和多線程中加互斥鎖同樣的東西,確保在一個事務結束以前,別的事務沒法對該數據進行操做。
下面是悲觀鎖的代碼,加鎖和解鎖都是須要消耗CPU資源的,因此在訂單併發少的狀況使用樂觀鎖會是一個更好的選擇。
''' 遇到問題沒人解答?小編建立了一個Python學習交流QQ羣:857662006 尋找有志同道合的小夥伴, 互幫互助,羣裏還有不錯的視頻學習教程和PDF電子書! '''
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':'訂單建立成功'})
複製代碼