Django - ORM - 事務, 樂觀鎖, 悲觀鎖

事務

概念

Transaction數據庫

事務:一個最小的不可再分的工做單元;一般一個事務對應一個完整的業務(例如銀行帳戶轉帳業務,該業務就是一個最小的工做單元)django

一個完整的業務須要批量的DML(insert、update、delete)語句共同聯合完成併發

事務只和DML語句 ( 數據庫操做語句 ) 有關,或者說DML語句纔有事務。這個和業務邏輯有關,業務邏輯不一樣,DML語句的個數不一樣高併發

特性

▧ 原子性(A)  事務是最小單位,不可再分post

▧ 一致性(C)  事務要求全部的DML語句操做的時候,必須保證同時成功或者同時失敗atom

▧ 隔離性(I)  事務A和事務B之間具備隔離性spa

▧ 持久性(D)  是事務的保證,事務終結的標誌(內存的數據持久到硬盤文件中)線程

行爲

▧ 開啓事務  Start Transactioncode

▧ 事務結束  End Transactionblog

▧ 提交事務  Commit Transaction

▧ 回滾事務  Rollback Transaction

標誌

開啓標誌

任何一條DML語句(insert、update、delete)執行,標誌事務的開啓

結束標誌

▧ 提交  成功的結束,將全部的DML語句操做歷史記錄和底層硬盤數據來一次同步

▧ 回滾  失敗的結束,將全部的DML語句操做歷史記錄所有清空

代碼庫

Django 自帶的代碼庫

from django.db import transaction

使用

方式一, 直接使用將一段操做設置爲事務

with transaction.atomic():
    ...

方式二, 裝飾器方式

@transaction.atomic
def foo():
    ....

悲觀鎖

概念

老是假設最壞的狀況,每次取數據時都認爲其餘線程會修改,因此都會加鎖(讀鎖、寫鎖、行鎖等)

當其餘線程想要訪問數據時,都須要阻塞掛起。能夠依靠數據庫實現,如行鎖、讀鎖和寫鎖等,都是在操做以前加鎖

保證同一時刻只有一個線程能操做數據,其餘線程則會被 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': '建立成功'})
相關文章
相關標籤/搜索