python數據庫並發處理(樂觀鎖)


1.數據庫並發處理問題

在多個用戶同時發起對同一個數據提交修改操作時(先查詢,再修改),會出現資源競爭的問題,導致最終修改的數據結果出現異常。

比如限量商品在熱銷時,當多個用戶同時請求購買商品時,最終修改的數據就會出現異常 
數據庫並發處理

下面我們來寫點代碼還原一下現象:

1.新建項目Optimistic locking,創建應用app01,編輯models創建一張表並執行數據庫遷移,如下:

from django.db import models
 
class GoodsInfo(models.Model):
    """ 商品 """
    name = models.CharField(max_length=50, verbose_name='名稱')
    stock = models.IntegerField(default=0, verbose_name='庫存')
 
    class Meta:
        db_table = 'tb_goodsinfo'

 

2.往數據庫中插入一條數據:insert into tb_goodsinfo values(0, "macbook", 10);

3.定義Goods視圖類,

  • 增加判斷庫存和修改庫存之間的間隙,就可以模擬出A用戶尚未修改庫存之前,B用戶已經開始進行判斷庫存,導致誤差:
from django.http import HttpResponse
from rest_framework.generics import GenericAPIView
from app01.models import GoodsInfo
 
class Goods(GenericAPIView):
    """ 購買商品 """
    def post(self, request):
        # 獲取請求頭中查詢字符串數據
        goods_id = request.GET.get('goods_id')
        count = int(request.GET.get('count'))
 
        # 查詢商品對象
        goods = GoodsInfo.objects.filter(id=goods_id).first()
        # 獲取原始庫存
        origin_stock = goods.stock
 
        # 判斷商品庫存是否充足
        if origin_stock < count:
            return HttpResponse(content="商品庫存不足", status=400)
 
        # 演示多個用戶並發請求
        import time
        time.sleep(5)
 
        # 減少商品的庫存數量,保存到數據庫
        goods.stock = origin_stock - count
        goods.save()
 
        return HttpResponse(content="操作成功", status=200)

 

我們先使用postman來模擬單個用戶請求

postman

  • 再來查詢數據庫,單個用戶請求正常,(將stock恢復到10) 
    查詢數據庫

模擬多個用戶請求

我們來使用兩個postman模擬A,B用戶同時請求,用戶A買6套商品,用戶B買5套商品

運行結果:

  • 輸出日志: 
    log
  • 查詢數據庫: 
    查詢數據庫
  • 兩個postman發出的post請求均提示 “操作成功”

分析及結論:

  • 當A用戶請求的時候,goods.stock = origin_stock - count 
    A操作的結果:goods.stock = 10 - 6 = 4

  • 可是B用戶判斷庫存的時候,A還未將修改的數據保存到數據庫,所以B獲取的庫存數量也是 10 
    B操作的結果:goods.stock = 10 - 5 = 5

  • 寫入數據庫操作中,B的數據將A的數據覆蓋,故最后的庫存還是 5

2.解決辦法:

如果使用給數據庫加鎖的方式,在給處理多個商品時可能會出現死鎖,所以使用數據庫中的樂觀鎖方式來處理效果較好

數據庫樂觀鎖: 
樂觀鎖並不是真實存在的鎖,而是在更新的時候判斷此時的庫存是否是之前查詢出的庫存,如果相同,表示沒人修改,可以更新庫存,否則表示別人搶過資源,不再執行庫存更新。類似如下操作

使用原生的SQL語句
update tb_goodsinfo set stock=5 where id=1 and stock=10;
 
使用Django中的語法
GoodsInfo.objects.filter(id=1, stock=10).update(stock=5)
 
# GoodsInfo:模型類,  id:商品id,  stock:庫存

 

改寫視圖類:

from django.http import HttpResponse
from rest_framework.generics import GenericAPIView
from app01.models import GoodsInfo
 
class Goods(GenericAPIView):
    """ 購買商品 """
    def post(self, request):
        # 獲取請求頭中查詢字符串數據
        goods_id = request.GET.get('goods_id')
        count = int(request.GET.get('count'))
 
        while True:
            # 查詢商品對象
            goods = GoodsInfo.objects.filter(id=goods_id).first()
            # 獲取原始庫存
            origin_stock = goods.stock
 
            # 判斷商品庫存是否充足
            if origin_stock < count:
                return HttpResponse(content="商品庫存不足", status=400)
 
            # 演示並發請求
            import time
            time.sleep(5)
 
            # 減少商品的庫存數量,保存到數據庫
            # goods.stock = origin_stock - count
            # goods.save()
            """ 使用樂觀鎖進行處理,一步完成數據庫的查詢和更新 """
            # update返回受影響的行數
            result = GoodsInfo.objects.filter(id=goods.id, stock=origin_stock).update(stock=origin_stock - count)
            if result == 0:
                # 表示更新失敗,有人搶先購買了商品,重新獲取庫存信息,判斷庫存
                continue
 
            # 表示購買成功,退出 while 循環
            break
 
        return HttpResponse(content="操作成功", status=200)

 

  • 結果:
  • 輸出日志: 
    log_cuccess
  • 查詢數據庫 
    查詢數據庫
  • A用戶返回 “操作成功”, B用戶返回 “商品庫存不足”

3.需要修改MySQL的事務隔離級別

事務隔離級別指的是在處理同一個數據的多個事務中,一個事務修改數據后,其他事務何時能看到修改后的結果。

MySQL數據庫事務隔離級別主要有四種:

Serializable 串行化,一個事務一個事務的執行
 
Repeatable read 可重復讀,無論其他事務是否修改並提交了數據,在這個事務中看到的數據值始終不受其他事務影響
 
Read committed 讀取已提交,其他事務提交了對數據的修改后,本事務就能讀取到修改后的數據值
 
Read uncommitted 讀取為提交,其他事務只要修改了數據,即使未提交,本事務也能看到修改后的數據值。

 

MySQL數據庫默認使用可重復讀( Repeatable read),而使用樂觀鎖的時候,如果一個事務修改了庫存並提交了事務,那其他的事務應該可以讀取到修改后的數據值,所以不能使用可重復讀的隔離級別,應該修改為讀取已提交Read committed。

修改方法:

  • 打開配置文件 
    打開mysql配置文件

  • 修改隔離級別 
    修改隔離級別

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM