Django transaction 誤用以後遇到的一個問題與解決方法

今天在調試項目開發好的一個模塊的時候,發現了一個很詭異的現象,最後追蹤發現是由於在項目中事務處理有誤所致。這個問題坑了我好一會,因此記錄一下,以避免再踩坑。下面開始詳述。數據庫

咱們都知道 Django 框架提供了不少的開啓事務的方式,這在後面會有詳述。筆者比較喜歡使用的是使用 @transaction.atomic 裝飾的方式來啓動一個事務。由於經過該形式,咱們能夠在保證了 db 原子操做的同時,還能夠自定義事務涉及的模塊範圍。atomic 還能夠經過上下文的形式來使用,好比:django

with transaction.atomic():
    transaction_plalala()
.
.
.

好了。既然如此,那就用起來吧。一陣啪啪啪以後,開發完了,調試的 log 也打了,開測吧。詭異的事情在此發生了。在log中明明打印出來了數據庫中生成的主鍵id,可是在數據庫中死活查不出來。WTF?!經過 SHOW CREATE TABLE xxx 也看到了 xxx 表的自增值已經發現了變化。但咋就沒有了呢?誰動了個人數據?這時候,不得不從 view 開始看起,一直追蹤到了開發的新的模塊。view到調用模塊沒有問題。這是什麼狀況?有鬼?確定不是。莫非在某個地方,配置了一個新的事務,而這個事務是包含了整個 view?由於筆者只發現了 view 中有個 raise exception的操做。猜想只能是這樣了。由於我新開發的模塊沒有問題的,這我是在其餘的 view 中進行過驗證的。框架

因而乎,筆者看了下 Django 中的事務開啓的方式,發現了果真有一個將事務綁定到 HTTP 請求上的開啓的方式。它的開啓方式是在 db 中指定 ATOMIC_REQUESTS=True 開啓。打開 settings 文件,找到了對應的配置,果真,問題就是出在這裏!咋辦?既然這邊人家已經配置了,在不影響到其餘 view(說不定已經依賴了此事務操做)的狀況下,怎麼去關閉這個配置呢?答案是經過 transaction.non_atomic_requests 裝飾view。好了。測試一下,確實能夠了。問題解決了。下面的是官方 Demo:ide

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 是依賴於應用程序對數據庫的查詢語句效率和數據庫當前的鎖競爭狀況。當流量上來的時候,性能會有影響。那麼,該怎麼去保證既可使用事務呢?性能

第一種就是上面所說的這種,在 database 中經過指定 ATOMIC_REQUESTS 的形式來將事務綁定到HTTP請求上。接觸 view 在事務中的操做的方式是在 view 上裝飾 transaction.non_atomic_requests,前面已經說過,具體也能夠閱讀 Django 官方文檔。測試

default_db = {
        "ENGINE": "",
        "NAME": "",
        "USER": "",
        "PASSWORD": "",
        "HOST": "",
        "PORT": "",
        "OPTIONS": "",
        "ATOMIC_REQUESTS": True,
    }

還有一種方式是筆者喜歡用的那種,經過 transaction.atomic 來更加明確的控制事務。atomic容許咱們在執行代碼塊時,在數據庫層面提供原子性保證。 若是代碼塊成功完成, 相應的變化會被提交到數據庫進行commit;若是執行期間遇到異常,則會將該段代碼所涉及的全部更改回滾。
這裏,咱們在使用此方式的時候還須要注意一點,就是:避免在 atomic裏捕獲異常!當一個原子塊執行完退出時,Django會審查是正常提交仍是回滾。若是你在原子塊中捕獲了異常的句柄, 你可能就向 Django 隱藏了問題的發生。這可能會致使意想不到的後果。正確捕捉數據庫異常應該是相似上文所講 ,基於atomic 代碼塊來作。如有必要,能夠額外增長一層atomic代碼來用於此目的。這種模式還有另外一個優點:它明確了當一個異常發生時,哪些操做將回滾。在底層,Django的事務管理代碼:atom

  • 當進入到最外層的 atomic 代碼塊時會打開一個事務;
  • 當進入到內層atomic代碼塊時會建立一個保存點;
  • 當退出內部塊時會釋放或回滾保存點;
  • 當退出外部塊時提交或回退事物。
    你能夠經過設置savepoint 參數爲 False來使對內層的保存點失效。若是異常發生,若設置了savepoint,Django會在退出第一層代碼塊時執行回滾,不然會在最外層的代碼塊上執行回滾。 原子性始終會在外層事物上獲得保證。這個選項僅僅用在設置保存點開銷很明顯時的狀況下。它的缺點是打破了上述錯誤處理的原則。
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()

Django 支持 autocommit 來爲每一個SQL語句在執行時都會啓動一個事務。若是想要關閉,能夠經過在配置文件中設置 AUTOCOMMIT=False 參數來關閉。這樣,Django 將不能啓用 autocommit,也不能執行任何 commits。這就須要你對每一個事物執行明確的commit操做。所以,這最好只用於你自定義的事物控制中間件或者是一些比較奇特的場景。調試

參考:code

相關文章
相關標籤/搜索