08.Django基礎六之ORM中的鎖和事務

来源:https://www.cnblogs.com/changxin7/archive/2019/09/27/11595555.html
-Advertisement-
Play Games

一 鎖 行級鎖 select_for_update(nowait=False, skip_locked=False) 註意必須用在事務裡面,至於如何開啟事務,我們看下麵的事務一節。 返回一個鎖住行直到事務結束的查詢集,如果資料庫支持,它將生成一個 SELECT ... FOR UPDATE 語句。 ...


一 鎖

  行級鎖

    select_for_update(nowait=False, skip_locked=False) #註意必須用在事務裡面,至於如何開啟事務,我們看下麵的事務一節。

    返回一個鎖住行直到事務結束的查詢集,如果資料庫支持,它將生成一個 SELECT ... FOR UPDATE 語句。

    舉個例子:

entries = Entry.objects.select_for_update().filter(author=request.user)  #加互斥鎖,由於mysql在查詢時自動加的是共用鎖,所以我們可以手動加上互斥鎖。create、update、delete操作時,mysql自動加行級互斥鎖

    所有匹配的行將被鎖定,直到事務結束。這意味著可以通過鎖防止數據被其它事務修改。

    一般情況下如果其他事務鎖定了相關行,那麼本查詢將被阻塞,直到鎖被釋放。 如果這不想要使查詢阻塞的話,使用select_for_update(nowait=True)。 如果其它事務持有衝突的鎖,互斥鎖, 那麼查詢將引發 DatabaseError 異常。你也可以使用select_for_update(skip_locked=True)忽略鎖定的行。 nowait和  skip_locked是互斥的,同時設置會導致ValueError。

    目前,postgresql,oracle和mysql資料庫後端支持select_for_update()。 但是,MySQL不支持nowait和skip_locked參數。

    使用不支持這些選項的資料庫後端(如MySQL)將nowait=True或skip_locked=True轉換為select_for_update()將導致拋出DatabaseError異常,這可以防止代碼意外終止。

  表鎖(瞭解)

class LockingManager(models.Manager):
    """ Add lock/unlock functionality to manager.

    Example::

        class Job(models.Model): #其實不用這麼負載,直接在orm創建表的時候,給這個表定義一個lock和unlock方法,藉助django提供的connection模塊來發送鎖表的原生sql語句和解鎖的原生sql語句就可以了,不用外層的這個LckingManager(model.Manager)類

            manager = LockingManager()

            counter = models.IntegerField(null=True, default=0)

            @staticmethod
            def do_atomic_update(job_id)
                ''' Updates job integer, keeping it below 5 '''
                try:
                    # Ensure only one HTTP request can do this update at once.
                    Job.objects.lock()

                    job = Job.object.get(id=job_id)
                    # If we don't lock the tables two simultanous
                    # requests might both increase the counter
                    # going over 5
                    if job.counter < 5:
                        job.counter += 1                                        
                        job.save()

                finally:
                    Job.objects.unlock()


    """    

    def lock(self):
        """ Lock table. 

        Locks the object model table so that atomic update is possible.
        Simulatenous database access request pend until the lock is unlock()'ed.

        Note: If you need to lock multiple tables, you need to do lock them
        all in one SQL clause and this function is not enough. To avoid
        dead lock, all tables must be locked in the same order.

        See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
        """
        cursor = connection.cursor()
        table = self.model._meta.db_table
        logger.debug("Locking table %s" % table)
        cursor.execute("LOCK TABLES %s WRITE" % table)
        row = cursor.fetchone()
        return row

    def unlock(self):
        """ Unlock the table. """
        cursor = connection.cursor()
        table = self.model._meta.db_table
        cursor.execute("UNLOCK TABLES")
        row = cursor.fetchone()
        return row  

二 事務

  

  關於MySQL的事務處理,我的mysql博客已經說的很清楚了,那麼我們來看看Django是如果做事務處理的。django1.8版本之前是有很多種添加事務的方式的,中間件的形式(全局的)、函數裝飾器的形式,上下文管理器的形式等,但是很多方法都在1.8版之後給更新了,下麵我們只說最新的:

1 全局開啟

    在Web應用中,常用的事務處理方式是將每個請求都包裹在一個事務中。這個功能使用起來非常簡單,你只需要將它的配置項ATOMIC_REQUESTS設置為True。

    它是這樣工作的:當有請求過來時,Django會在調用視圖方法前開啟一個事務。如果請求卻正確處理並正確返回了結果,Django就會提交該事務。否則,Django會回滾該事務。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mxshop',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        'OPTIONS': {
            "init_command": "SET default_storage_engine='INNODB'",
       #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置開啟嚴格sql模式


        }
        "ATOMIC_REQUESTS": True, #全局開啟事務,綁定的是http請求響應整個過程        "AUTOCOMMIT":False, #全局取消自動提交,慎用
    },  'other':{    'ENGINE': 'django.db.backends.mysql',             ......  } #還可以配置其他資料庫
}

    上面這種方式是統一個http請求對應的所有sql都放在一個事務中執行(要麼所有都成功,要麼所有都失敗)。是全局性的配置, 如果要對某個http請求放水(然後自定義事務),可以用non_atomic_requests修飾器,那麼他就不受事務的管控了

from django.db import transaction

@transaction.non_atomic_requests
def my_view(request):
    do_stuff()

@transaction.non_atomic_requests(using='other')
def my_other_view(request):
    do_stuff_on_the_other_database()

    但是Django 文檔中說,不推薦這麼做。因為如果將事務跟 HTTP 請求綁定到一起的時,然而view 是依賴於應用程式對資料庫的查詢語句效率和資料庫當前的鎖競爭情況。當流量上來的時候,性能會有影響,知道一下就行了

    所以推薦用下麵這種方式,通過 transaction.atomic 來更加明確的控制事務。atomic允許我們在執行代碼塊時,在資料庫層面提供原子性保證。 如果代碼塊成功完成, 相應的變化會被提交到資料庫進行commit;如果執行期間遇到異常,則會將該段代碼所涉及的所有更改回滾。

2 局部使用事務

    atomic(using=None, savepoint=True)[source] ,參數:using='other',就是當你操作其他資料庫的時候,這個事務才生效,看上面我們的資料庫配置,除了default,還有一個other,預設的是default。savepoint的意思是開啟事務保存點,推薦看一下我資料庫博客裡面的事務部分關於保存點的解釋。

原子性是資料庫事務的一個屬性。使用atomic,我們就可以創建一個具備原子性的代碼塊。一旦代碼塊正常運行完畢,所有的修改會被提交到資料庫。反之,如果有異常,更改會被回滾。

    被atomic管理起來的代碼塊還可以內嵌到方法中。這樣的話,即便內部代碼塊正常運行,如果外部代碼塊拋出異常的話,它也沒有辦法把它的修改提交到資料庫中。

    用法1:給函數做裝飾器來使用 

from django.db import transaction

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

    用法2:作為上下文管理器來使用,其實就是設置事務的保存點

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()

    do_other_stuff()

      一旦把atomic代碼塊放到try/except中,完整性錯誤就會被自然的處理掉了,比如下麵這個例子:

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

    用法3:還可以嵌套使用,函數的事務嵌套上下文管理器的事務,上下文管理器的事務嵌套上下文管理器的事務等。下麵的是函數嵌套上下文的例子:

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()       #other_task()  #還要註意一點,如果你在事務裡面寫了別的操作,只有這些操作全部完成之後,事務才會commit,也就是說,如果你這個任務是查詢上面更改的數據表裡面的數據,那麼看到的還是事務提交之前的數據。
    except IntegrityError:
        handle_exception()

    add_children()

      這個例子中,即使generate_relationships()中的代碼打破了數據完整性約束,你仍然可以在add_children()中執行資料庫操作,並且create_parent()產生的更改也有效。需要註意的是,在調用handle_exception()之前,generate_relationships()中的修改就已經被安全的回滾了。因此,如果有需要,你照樣可以在異常處理函數中操作資料庫。

儘量不要在atomic代碼塊中捕獲異常

  因為當atomic塊中的代碼執行完的時候,Django會根據代碼正常運行來執行相應的提交或者回滾操作。如果在atomic代碼塊裡面捕捉並處理了異常,就有可能隱蓋代碼本身的錯誤,從而可能會有一些意料之外的不愉快事情發生。

  擔心主要集中在DatabaseError和它的子類(如IntegrityError)。如果這種異常真的發生了,事務就會被破壞掉,而Django會在代碼運行完後執行回滾操作。如果你試圖在回滾前執行一些資料庫操作,Django會拋出TransactionManagementError。通常你會在一個ORM相關的信號處理器拋出異常時遇到這個行為。

捕獲異常的正確方式正如上面atomic代碼塊所示。如果有必要,添加額外的atomic代碼塊來做這件事情,也就是事務嵌套。這麼做的好處是:當異常發生時,它能明確地告訴你那些操作需要回滾,而那些是不需要的。

    為了保證原子性,atomic還禁止了一些API。像試圖提交、回滾事務,以及改變資料庫連接的自動提交狀態這些操作,在atomic代碼塊中都是不予許的,否則就會拋出異常。

  下麵是Django的事務管理代碼:

  • 進入最外層atomic代碼塊時開啟一個事務;
  • 進入內部atomic代碼塊時創建保存點;
  • 退出內部atomic時釋放或回滾事務;註意如果有嵌套,內層的事務也是不會提交的,可以釋放(正常結束)或者回滾
  • 退出最外層atomic代碼塊時提交或者回滾事務;

    你可以將保存點參數設置成False來禁止內部代碼塊創建保存點。如果發生了異常,Django在退出第一個父塊的時候執行回滾,如果存在保存點,將回滾到這個保存點的位置,否則就是回滾到最外層的代碼塊。外層事務仍然能夠保證原子性。然而,這個選項應該僅僅用於保存點開銷較大的時候。畢竟它有個缺點:會破壞上文描述的錯誤處理機制。

  註意:transaction只對資料庫層的操作進行事務管理,不能理解為python操作的事務管理

def example_view(request):
    tag = False
    with transaction.atomic():
        tag = True
        change_obj() # 修改對象變數
        obj.save()
        raise DataError
    print("tag = ",tag) #結果是True,也就是說在事務中的python變數賦值,即便是事務回滾了,這個賦值也是成功的

  還要註意:如果你配置了全局的事務,它和局部事務可能會產生衝突,你可能會發現你局部的事務完成之後,如果你的函數裡面其他的sql除了問題,也就是沒在這個上下文管理器的局部事務包裹範圍內的函數裡面的其他的sql出現了問題,你的局部事務也是提交不上的,因為全局會回滾這個請求和響應所涉及到的所有的sql,所以還是建議以後的項目儘量不要配置全局的事務,通過局部事務來搞定,當然了,看你們的業務場景。

transaction的其他方法

@transaction.atomic
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()  #創建保存點

  b.save()
  # open transaction now contains a.save() and b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid) #提交保存點
      # open transaction still contains a.save() and b.save()
  else:
      transaction.savepoint_rollback(sid)  #回滾保存點
      # open transaction now contains only a.save()

  transaction.commit() #手動提交事務,預設是自動提交的,也就是說如果你沒有設置取消自動提交,那麼這句話不用寫,如果你配置了那個AUTOCOMMIT=False,那麼就需要自己手動進行提交。

  為保證事務的隔離性,我們還可以結合上面的鎖來實現,也就是說在事務裡面的查詢語句,咱們使用select_for_update顯示的加鎖方式來保證隔離性,事務結束後才會釋放這個鎖,例如:(瞭解)

@transaction.atomic ## 輕鬆開啟事務
def handle(self):
    ## 測試是否存在此用戶
    try:
        ## 鎖定被查詢行直到事務結束
        user = 
    User.objects.select_for_update().get(open_id=self.user.open_id)
        #other sql 語句
    except User.DoesNotExist:
        raise BaseError(-1, 'User does not exist.')
    

  通過Django外部的python腳本來測試一下事務:

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
    import django
    django.setup()

    import datetime
    from app01 import models

    try:
        from django.db import transaction
        with transaction.atomic():
            new_publisher = models.Publisher.objects.create(name="火星出版社")
            models.Book.objects.create(title="橘子物語", publish_date=datetime.date.today(), publisher_id=10)  # 指定一個不存在的出版社id
    except Exception as e:
        print(str(e))

  下麵再說一些設置事務的小原則吧:

    1.保持事務短小
    2.儘量避免事務中rollback
    3.儘量避免savepoint
    4.預設情況下,依賴於悲觀鎖
    5.為吞吐量要求苛刻的事務考慮樂觀鎖
    6.顯示聲明打開事務
    7.鎖的行越少越好,鎖的時間越短越好


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 場景 Docker-Compose簡介與Ubuntu Server 上安裝Compose: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100902301 Docker Compose基本使用-使用Compose啟動Tomcat ...
  • 簡單工廠模式 概念 簡單工廠模式屬於創建型模式,又叫做靜態工廠方法(Static Factory Method)。簡單工廠模式是由一個工廠對象決定創建哪一種產品類實例。在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的 ...
  • 一、設計模式(Design Pattern): 1.設計模式的概念 是前輩們對代碼開發經驗的總結,是解決特定問題的一系列套路。它不是語法規定,而是一套用來提高代碼可復用性、可維護性、可讀性、穩健性以及安全性的解決方案。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 2.學習設 ...
  • 外觀模式(Facade): 外部通過一個統一的介面,訪問子系統中的一群介面。外觀模式定義了一個高層介面,為子系統中的一組介面提供一個一致的入口,使得子系統更容易使用。外觀模式相對比較簡單,可以理解為中介,原先租房需要自己一個個篩選,聯繫房東,談好價格,簽合同等等,現在不需要這些了,只要你說出要求中介 ...
  • 場景 不知道大家有沒有遇到這樣的情況,就是去自動取款機取錢的時候,比如說你去取1000塊錢,這個時候系統會先幫你把1000塊錢扣除,然後自動取款機再把錢吐出來。但是如果取款機出現問題,會發現錢被扣了,但是錢沒有取出來。我第一次遇到這個問題的時候很擔心,當時跨行取取了3000塊錢,簡訊提醒我錢已經被扣 ...
  • 此文前端框架使用 "rax" ,全篇代碼暫未開源(待開源) 原文鏈接地址: "Nealyang/PersonalBlog" 前言 貌似在面試中,你如果設計一個 react/vue 組件,貌似已經是司空見慣的問題了。本文不是理論片,更多的是自己的一步步思考和實踐。文中會有很多筆者的思考過程,歡迎評論區 ...
  • 前言 今天我們一起來看行為型設計模式中的命令模式、何為命令模式呢?先談命令——我現在需要對某一條信息進行刪除,我進行點擊刪除按鈕。後臺執行刪除的命令、對信息進行刪除。那麼我們要講的命令模式又是什麼呢?命令模式就是把一個操作或者行為抽象為一個對象。然後通過對命令的抽象化來使得發出命令的職責和執行命令的 ...
  • 概述 簡單介紹一下七大設計原則: 1. 開閉原則 :是所有面向對象設計的核心,對擴展開放,對修改關閉 2. 依賴倒置原則 :針對介面編程,依賴於抽象而不依賴於具體 3. 單一職責原則 :一個介面只負責一件事情,只能有一個原因導致類變化 4. 介面隔離原則 :使用多個專門的介面,而不是使用一個總介面 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...