在多個用戶同時When initiating a commit modification operation on the same data(先查詢,再修改),There will be a problem of resource competition,The result of the final modified data is abnormal.
For example, when a limited-edition item is on sale,When multiple users request to purchase an item at the same time,The final modified data will be abnormal
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RWpbej13-1636196467076)(2018082423563121)]
實例一:
數據庫模型:
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'
視圖:
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)
# Demonstrate concurrent requests by multiple users
import time
time.sleep(5)
# 減少商品的庫存數量,保存到數據庫
goods.stock = origin_stock - count
goods.save()
return HttpResponse(content="操作成功", status=200)
Let's use twopostman模擬A,BUser requests at the same time,用戶A買6set of goods,用戶B買5set of goods
運行結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iKs6oNW9-1636196467078)(20180825024104689)]
查詢數據庫:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dKwicobl-1636196467080)(20180825024359382)]
A操作的結果:goods.stock = 10 - 6 = 4
B操作的結果:goods.stock = 10 - 5 = 5
數據庫樂觀鎖:
樂觀鎖並不是真實存在的鎖,而是在更新的時候判斷此時的庫存是否是之前查詢出的庫存,如果相同,表示沒人修改,可以更新庫存,否則表示別人搶過資源,不再執行庫存更新.類似如下操作
使用原生的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:庫存
Override the view:
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)
結果:
A用戶返回 “操作成功”, B用戶返回 “商品庫存不足”
使用數據版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式.何謂數據版本?即為數據增加一個版本標識,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現.當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加一.當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對,如果數據庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期數據
# versionValueis the obtained version number
result = GoodsInfo.objects.filter(id=goods.id, stock=versionValue).update(stock=origin_stock - count)
數據庫悲觀鎖
概念:
總是假設最壞的情況,每次取數據時都認為其他線程會修改,所以都會加鎖(讀鎖、寫鎖、行鎖等)
當其他線程想要訪問數據時,都需要阻塞掛起.可以依靠數據庫實現,如行鎖、讀鎖和寫鎖等,都是在操作之前加鎖
保證同一時刻只有一個線程能操作數據,其他線程則會被 block
運用場景:
▧ 無髒讀 The locked data is guaranteed to be consistent, Hence no dirty reads, Pessimistic locking for environments where dirty reads are not allowed can do the trick
▧ 無並行 Pessimistic locking guarantees transaction success, However, it will lock the data, which makes it impossible to realize the parallel processing of the data.
▧ The transaction success rate is high Locking guarantees one success, Therefore, it is more suitable for pessimistic locking when the success rate of data processing is required to be high.
▧ 開銷大 There is overhead in locking and unlocking pessimistic locks, If the amount of concurrency is too large, this overhead cannot be underestimated, Therefore, it is not suitable to use pessimistic locking in a high concurrency environment
▧ 一次性完成 If optimistic locking is attempted multiple times, the cost is relatively large,Pessimistic locking is also recommended, Pessimistic locking guarantees one success
代碼
select_for_update()`This method has two default parameters,`nowait=False`和`skip_locked=False
nowait
The meaning is to not wait while the matching record is locked,會拋異常.但是 MySQL8.0 以前不支持.
skip_locked
的含義是SELECTWhen skipping locked records.
select_for_update()
The method must be applied in a transaction,可利用@transaction.atomic()
Decorators wrap view functions
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 means lock,只有獲取到鎖才會執行查詢,否則阻塞等待.
obj = 模型類名.objects.select_for_update().get(id=1)
# 等事務提交後,會自動釋放鎖.
return HttpResponse('ok')
ShowMeAI日報系列全新升級!覆蓋AI人工智能 工具&a