事務,悲觀鎖和樂觀鎖
事務
事務(Transaction):一個最小的不可再分的工作單元;通常一個事務對應一個完整的業務(例如銀行賬戶轉賬業務,該業務就是一個最小的工作單元)
一個完整的業務需要批量的DML(insert、update、delete)語句共同聯合完成
事務只和DML語句 ( 資料庫操作語句 ) 有關,或者說DML語句才有事務。這個和業務邏輯有關,業務邏輯不同,DML語句的個數不同
特性
-
原子性(A) 事務是最小單位,不可再分
-
一致性(C) 事務要求所有的DML語句操作的時候,必須保證同時成功或者同時失敗
-
隔離性(I) 事務A和事務B之間具有隔離性
-
永續性(D) 是事務的保證,事務終結的標誌(記憶體的資料持久到硬碟檔案中)
行為
開啟事務 Start Transaction
事務結束 End Transaction
提交事務 Commit Transaction
回滾事務 Rollback Transaction
django開啟事務
from django.db import transaction # 方式一 with transaction.atomic(): # 開啟事務,當with語句執行完成以後,自動提交事務 ... # 資料庫操作 # 方式二 @transaction.atomic def foo(): .... sid=transaction.savepoint() #開啟事務 ... transaction.savepoint_rollback(sid) # 回滾 ... transaction.savepoint_commit(sid) # 提交 # 事務回滾 from django.db import transaction from rest_framework.views import APIView class OrderAPIView(APIView): def post(self,request): .... with transation.atomic(): # 設定事務回滾的標記點 sid = transation.savepoint() .... try: .... except: transation.savepoint_rallback(sid)
悲觀鎖
總是假設最壞的情況,每次取資料時都認為其他執行緒會修改,所以都會加鎖(讀鎖、寫鎖、行鎖等)
當其他執行緒想要訪問資料時,都需要阻塞掛起。可以依靠資料庫實現,如行鎖、讀鎖和寫鎖等,都是在操作之前加鎖
保證同一時刻只有一個執行緒能操作資料,其他執行緒則會被 block
運用場景
▧ 無髒讀 上鎖資料保證一致, 因此無髒讀, 對髒讀不允許的環境悲觀鎖可以勝任
▧ 無並行 悲觀鎖對事務成功性可以保證, 但是會對資料加鎖導致無法實現資料的並行處理.
▧ 事務成功率高 上鎖保證一次成功, 因此在對資料處理的成功率要求較高的時候更適合悲觀鎖.
▧ 開銷大 悲觀鎖的上鎖解鎖是有開銷的, 如果超大的併發量這個開銷就不容小視, 因此不適合在高併發環境中使用悲觀鎖
▧ 一次性完成 如果樂觀鎖多次嘗試的代價比較大,也建議使用悲觀鎖, 悲觀鎖保證一次成功
使用
from django.shortcuts import render
from django.http import HttpResponse
from django.views.generic import View
from django.db import transaction
from 應用名.models import 模型類名
# 類檢視 (併發,悲觀鎖)
class MyView(View):
@transaction.atomic
def post(self, request):
# select * from 表名 where id=1 for update;
# for update 就表示鎖,只有獲取到鎖才會執行查詢,否則阻塞等待。
obj = 模型類名.objects.select_for_update().get(id=1)
# 等事務提交後,會自動釋放鎖。
return HttpResponse('ok')
樂觀鎖
總是認為不會產生併發問題,每次去取資料的時候總認為不會有其他執行緒對資料進行修改,因此不會上鎖
但是在更新時會判斷其他執行緒在這之前有沒有對資料進行修改,一般會使用版本號機制或CAS操作實現。
如果發現數據被改了. 就進行事務回滾取消之前的操作
運用場景
▧ 髒讀 樂觀鎖不涉及到上鎖的處理, 因此在資料並行需求的時候是更適合樂觀鎖,當然會產生髒讀, 不過用回滾取消掉了.
▧ 高併發 相比起悲觀鎖的開銷, 樂觀鎖也是比悲觀鎖更適合於高併發場景
▧ 事務成功率低 樂觀鎖不能保證每次事務的成功, 是使用回滾方式來保證資料一致性, 因此會導致事務成功率很低.
▧ 讀多寫少 樂觀鎖適用於讀多寫少的應用場景,這樣可以提高併發粒度
▧ 開銷小 可能會導致很多次的回滾都不能拿到正確的處理迴應, 因此如果對成功性要求低,而且每次開銷小比較適合樂觀鎖
使用
from django.shortcuts import render
from django.http import JsonResponse
from django.views.generic import View
from django.db import transaction
from 應用名.models import GoodsSKU
# 類檢視 (併發,樂觀鎖)
class MyView(View):
@transaction.atomic
def post(self, request):
'''訂單建立'''
count = 3 # 訂購3件商品
# 設定事務儲存點
s1 = transaction.savepoint()
# 樂觀鎖,最多嘗試5次
for i in range(5):
# 查詢商品的資訊(庫存)
try:
sku = GoodsSKU.objects.get(id=1)
except:
# 商品不存在
transaction.savepoint_rollback(s1)
return JsonResponse({'res': 1, 'errmsg': '商品不存在'})
# 判斷商品的庫存
if count > sku.stock:
transaction.savepoint_rollback(s1)
return JsonResponse({'res': 2, 'errmsg': '商品庫存不足'})
# 更新商品的庫存和銷量
orgin_stock = sku.stock # 原庫存 (資料庫隔離級別必須是Read Committed;如果是Repeatable Read,那麼多次嘗試讀取的原庫存都是一樣的,讀不到其他執行緒提交更新後的資料。)
new_stock = orgin_stock - count # 更新後的庫存
new_sales = sku.sales + count # 更新後的銷量
# update 商品表 set stock=new_stock, sales=new_sales where id=1 and stock = orgin_stock
# 通過where子句中的條件判斷庫存是否進行了修改。(併發,樂觀鎖)
# 返回受影響的行數
res = GoodsSKU.objects.filter(id=1, stock=orgin_stock).update(stock=new_stock, sales=new_sales)
if res == 0: # 如果修改失敗
if i == 4:
# 如果嘗試5次都失敗
transaction.savepoint_rollback(s1)
return JsonResponse({'res': 3, 'errmsg': '下單失敗'})
continue # 再次嘗試
# 否則更新成功
# 跳出嘗試迴圈
break
# 提交事務
transaction.savepoint_commit(s1)
# 返回應答
return JsonResponse({'res': 4, 'message': '建立成功'})