基于Django的樂觀鎖與悲觀鎖解決訂單并發(fā)問題詳解
前言
訂單并發(fā)這個問題我想大家都是有一定認識的,這里我說一下我的一些淺見,我會盡可能的讓大家了解如何解決這類問題。
在解釋如何解決訂單并發(fā)問題之前,需要先了解一下什么是數(shù)據(jù)庫的事務(wù)。(我用的是mysql數(shù)據(jù)庫,這里以mysql為例)
1) 事務(wù)概念
一組mysql語句,要么執(zhí)行,要么全不不執(zhí)行。
2) mysql事務(wù)隔離級別
Read Committed(讀取提交內(nèi)容)
如果是Django2.0以下的版本,需要去修改到這個隔離級別,不然樂觀鎖操作時無法讀取已經(jīng)被修改的數(shù)據(jù)
RepeatableRead(可重讀)
這是這是Mysql默認的隔離級別,可以到mysql的配置文件中去修改;
transcation-isolation = READ-COMMITTED
在mysql配置文件中添加這行然后重啟mysql就可以將事務(wù)隔離級別修改至Read Committed
其他事務(wù)知識這里不會用到就不浪費時間去做介紹了。
悲觀鎖:開啟事務(wù),然后給mysql的查詢語句最后加上for update。
這是在干什么呢。可能大家有些不理解,其實就是給資源加上和多線程中加互斥鎖一樣的東西,確保在一個事務(wù)結(jié)束之前,別的事務(wù)無法對該數(shù)據(jù)進行操作。
下面是悲觀鎖的代碼,加鎖和解鎖都是需要消耗CPU資源的,所以在訂單并發(fā)少的情況使用樂觀鎖會是一個更好的選擇。
class OrderCommitView(View):
"""悲觀鎖"""
# 開啟事務(wù)裝飾器
@transaction.atomic
def post(self,request):
"""訂單并發(fā) ———— 悲觀鎖"""
# 拿到商品id
goods_ids = request.POST.getlist('goods_ids')
# 校驗參數(shù)
if len(goods_ids) == 0 :
return JsonResponse({'res':0,'errmsg':'數(shù)據(jù)不完整'})
# 當(dāng)前時間字符串
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':'地址錯誤'})
# 商品數(shù)量
total_count = 0
# 商品總價
total_amount = 0
# 獲取redis連接
conn = get_redis_connection('default')
# 拼接key
cart_key = 'cart_%d' % request.user.id
#
# 創(chuàng)建保存點
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:
# 嘗試查詢商品
# 此處考慮訂單并發(fā)問題,
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':'商品信息錯誤'})
# 取出商品數(shù)量
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
# 保存到數(shù)據(jù)庫
goods.save()
OrderGoods.objects.create(
order = order_info,
goods = goods,
count = count,
price = goods.price
)
# 累加商品件數(shù)
total_count += count
# 累加商品總價
total_amount += (goods.price) * count
# 更新訂單信息中的商品總件數(shù)
order_info.total_count = total_count
# 更新訂單信息中的總價格
order_info.total_price = total_amount + order_info.transit_price
order_info.save()
# 事務(wù)提交
transaction.commit()
return JsonResponse({'res':5,'errmsg':'訂單創(chuàng)建成功'})
然后就是樂觀鎖查詢了,相比悲觀鎖,樂觀鎖其實并不能稱為是鎖,那么它是在做什么事情呢。
其實是在你要進行數(shù)據(jù)庫操作時先去查詢一次數(shù)據(jù)庫中商品的庫存,然后在你要更新數(shù)據(jù)庫中商品庫存時,將你一開始查詢到的庫存數(shù)量和商品的ID一起作為更新的條件,當(dāng)受影響行數(shù)返回為0時,說明沒有修改成功,那么就是說別的進程修改了該數(shù)據(jù),那么你就可以回滾到之前沒有進行數(shù)據(jù)庫操作的時候,重新查詢,重復(fù)之前的操作一定次數(shù),如果超過你設(shè)置的次數(shù)還是不能修改那么就直接返回錯誤結(jié)果。
該方法只適用于訂單并發(fā)較少的情況,如果失敗次數(shù)過多,會帶給用戶不良體驗,同時適用該方法要注意數(shù)據(jù)庫的隔離級別一定要設(shè)置為Read Committed 。
最好在使用樂觀鎖之前查看一下數(shù)據(jù)庫的隔離級別,mysql中查看事物隔離級別的命令為
select @@global.tx_isolation;
class OrderCommitView(View):
"""樂觀鎖"""
# 開啟事務(wù)裝飾器
@transaction.atomic
def post(self,request):
"""訂單并發(fā) ———— 樂觀鎖"""
# 拿到id
goods_ids = request.POST.get('goods_ids')
if len(goods_ids) == 0 :
return JsonResponse({'res':0,'errmsg':'數(shù)據(jù)不完整'})
# 當(dāng)前時間字符串
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':'地址錯誤'})
# 商品數(shù)量
total_count = 0
# 商品總價
total_amount = 0
# 訂單運費
transit_price = 10
# 創(chuàng)建保存點
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:
# 嘗試查詢商品
# 此處考慮訂單并發(fā)問題,
# redis中取出商品數(shù)量
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):
# 若存在訂單并發(fā)則嘗試下單三次
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
# # 保存到數(shù)據(jù)庫
# 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)
# 累加商品件數(shù)
total_count += count
# 累加商品總價
total_amount += (goods.price) * count
# 更新訂單信息中的商品總件數(shù)
order_info.total_count = total_count
# 更新訂單信息中的總價格
order_info.total_price = total_amount + order_info.transit_price
order_info.save()
# 事務(wù)提交
transaction.savepoint_commit(sid)
return JsonResponse({'res':6,'errmsg':'訂單創(chuàng)建成功'})
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python pyttsx3庫實現(xiàn)文本轉(zhuǎn)語音功能的示例
pyttsx3是一個功能強大且易于使用的文本轉(zhuǎn)語音庫,適合需要離線語音合成的場景,本文就來介紹一下Python pyttsx3庫實現(xiàn)文本轉(zhuǎn)語音功能的示例,感興趣的可以了解一下2025-04-04
淺談python str.format與制表符\t關(guān)于中文對齊的細節(jié)問題
今天小編就為大家分享一篇淺談python str.format與制表符\t關(guān)于中文對齊的細節(jié)問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-01-01
Python?reversed函數(shù)用法小結(jié)
reversed函數(shù)是Python中的內(nèi)置函數(shù)之一,是對給定的序列返回一個逆序序列的迭代器,需要通過遍歷/list/next()等方法獲取作用后的值,本文給大家介紹Python?reversed函數(shù)及用法,感興趣的朋友一起看看吧2023-10-10
修復(fù)Python?Pandas數(shù)據(jù)標(biāo)記錯誤的幾種方法總結(jié)
用于分析數(shù)據(jù)的?Python?庫稱為?Pandas,在?Pandas?中讀取數(shù)據(jù)最常見的方式是通過?CSV?文件,但?CSV?文件的限制是它應(yīng)該采用特定的格式,否則在標(biāo)記數(shù)據(jù)時會拋出錯誤,在本文中,我們將討論修復(fù)?Python?Pandas?錯誤標(biāo)記數(shù)據(jù)的各種方法2023-10-10
python中使用smtplib和email模塊發(fā)送郵件實例
python腳本發(fā)郵件,一般會用到smtplib和email這兩個模塊??纯丛撃K怎么使用,先看smtplib模塊。 smtplib模塊定義了一個簡單的SMTP客戶端,可以用來在互聯(lián)網(wǎng)上發(fā)送郵件2014-04-04

