如何在 Django 中保證並發的數據一致性


1. 關於鎖

1.1 樂觀鎖

樂觀鎖的出發點是,同一條數據很少會因為並發修改而產生沖突,適用於讀多寫少的場景,用以提高吞吐量。

實現方式,讀取一個字段,執行處理邏輯,當需要更新數據時,再次檢查該字段是否和第一次讀取一致。如果一致,則更新數據,否則拒絕更新,重新讀取后再提交。

1.2 悲觀鎖

悲觀鎖的出發點是,當一條數據正在被修改時,不允許其他任何關於這條數據的操作。

實現方式,讀取一個字段之后,加鎖,不允許其他任何讀、寫操作。執行處理邏輯,更新數據完畢后,釋放鎖。

1.3 比較

樂觀鎖的開銷遠低於悲觀鎖。

悲觀鎖可能會導致死鎖。當 A 鎖定了 a 資源,需要 b 資源。而 b 被 B 鎖定,正在等待 a 資源。此時,導致出現死鎖。但是,可以通過設置超時來處理這個問題。

悲觀鎖可以有效降低沖突后,重試的次數。

樂觀鎖可以提高響應速度。

2. Django 中的事務

Django 默認每條數據庫操作都會被立即提交到數據庫。

這樣會導致一個問題,如果有一系列的數據庫操作構成,要么全部執行,要么就全部都不執行,怎么辦?

這時,就需要事務。將一系列數據庫操作設置為一個事務,提交給數據庫執行。

Django 提供 atomic 裝飾器以開啟事務。 atomic 使用一個參數來指定數據庫的名字。如果不設置值,Django 就會使用系統默認的數據庫。

2.1 整個 View 函數開啟事務

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

 

2.2 部分函數 do_more_stuff() 開啟事務。

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

 

2.3 不要在事務中處理異常

from django.db import transaction

def viewfunc(request):
    do_stuff()
    try:
        with transaction.commit_on_success():
            do_more_stuff_1() # in transaction
            try:
                do_more_stuff_2() # not in transaction
            except:
                pass
            do_more_stuff_3() # in transaction
    except:
        pass

 

 

當退出原子塊時,Django 會查看它是否正常退出或者是否有異常來確定是否提交或者回滾。

如果你捕獲並處理原子塊中的異常,可以能會隱藏 Django 中發生問題的事實。這樣可能會造成非預期的行為。

 

3. 利用 F 函數更新運算

通常更新數據庫的操作,需要將對象讀取到內存。在內存中進行修改之后,再寫回數據庫。

在內存中的操作,如果存在同時操作的情況,會導致運算邏輯錯誤。

F() 函數的作用就是直接生成 SQL 語句,不必將需要更新的對象讀取到內存。避免了並發導致的數據不一致問題。

from django.db.models import F

reporter = Reporters.objects.get(name='OICQ')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()

 

 

4. 利用 select_for_update 函數

select_for_update 使用的是悲觀鎖。

select for update 函數使用數據庫查詢語句,select ... for update 對數據庫進行操作。

這是數據庫層面的,解決並發取數據后再修改的問題方法。

 

def mark_as_readed(self, notification_id):
    # 讓s elect for update 和 update 語句發生在一個完整的事務里面
    with transaction.commit_on_success():
        # 使用select_for_update 來保證並發請求同時只有一個請求在處理,其他的請求
        # 等待鎖釋放
        notification = Notification.objects.select_for_update().get(pk=notification_id)
        # 沒有必要重復標記一個已經讀過的通知            
        if notication.has_readed:                
            return
        notification.has_readed = True
        notification.save()
        # 在這里更新我們的計數器,嗯,我感覺好極了
        self.update_unread_count(-1)

 


免責聲明!

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



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