Django4.0 數(shù)據(jù)庫事務(wù)-提交后

2022-03-16 17:37 更新

有時你需要執(zhí)行與當(dāng)前數(shù)據(jù)庫事務(wù)相關(guān)的操作,但前提是事務(wù)成功提交。

Django 提供了 on_commit() 函數(shù)來注冊在事務(wù)成功提交后應(yīng)該執(zhí)行的回調(diào)函數(shù):

on_commit(func, using=None)

將任意函數(shù)(無參數(shù))傳遞給 ?on_commit()?:

from django.db import transaction

def do_something():
    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

你也可以使用 ?lambda?包裝函數(shù):

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

傳入的函數(shù)將在成功提交調(diào)用“?on_commit()?”的假設(shè)數(shù)據(jù)庫寫操作后立即被調(diào)用。
無任何活動事務(wù)時調(diào)用 ?on_commit()? ,則回調(diào)函數(shù)會立即執(zhí)行。
如果假設(shè)的數(shù)據(jù)庫寫入被回滾(尤其是在 ?atomic()? 塊里引發(fā)了一個未處理異常),函數(shù)將被丟棄且永遠不會被調(diào)用。

保存點

正確處理保存點(即嵌套了 ?atomic()? 塊)。也就是說,注冊在保存點后的 ?on_commit()?  的調(diào)用(嵌套在 ?atomic()? 塊)將在外部事務(wù)被提交之后調(diào)用,但如果在事務(wù)期間回滾到保存點或任何之前的保存點之前,則不會調(diào)用:

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    with transaction.atomic():  # Inner atomic block, create a savepoint
        transaction.on_commit(bar)

# foo() and then bar() will be called when leaving the outermost block

另一方面,當(dāng)保存點回滾時(因引發(fā)異常),內(nèi)部調(diào)用不會被調(diào)用:

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    try:
        with transaction.atomic():  # Inner atomic block, create a savepoint
            transaction.on_commit(bar)
            raise SomeError()  # Raising an exception - abort the savepoint
    except SomeError:
        pass

# foo() will be called, but not bar()

執(zhí)行順序

事務(wù)提交后的的回調(diào)函數(shù)執(zhí)行順序與當(dāng)初注冊時的順序一致。

異常處理

如果一個帶有給定事務(wù)的 ?on-commit? 函數(shù)引發(fā)了未捕獲的異常,那么同一個事務(wù)里的后續(xù)注冊函數(shù)不會被運行。這與你在沒有 ?on_commit()? 的情況下順序執(zhí)行函數(shù)的行為是一樣的。

執(zhí)行時間

你的回調(diào)會在成功提交之后執(zhí)行,因此回調(diào)里的錯誤引發(fā)事務(wù)回滾。它們在事務(wù)成功時有條件的執(zhí)行,但它們不是事務(wù)的一部分。對于有預(yù)期的用例(郵件提醒,Celery 任務(wù)等),這樣應(yīng)該沒啥問題。如果它不是這樣的用例(如果你的后續(xù)操作很關(guān)鍵,以至于它的錯誤意味著事務(wù)失?。?,那么你可能不需要使用 ?on_commit()? 鉤子。相反,你可能需要兩階段提交——比如兩階段提交協(xié)議支持( psycopg Two-Phase Commit protocol support )和在 Python DB-API 里說明的可選兩階段提交擴展( optional Two-Phase Commit Extensions in the Python DB-API specification ) 。
直到在提交后的連接上恢復(fù)自動提交,調(diào)用才會運行。(因為否則在回調(diào)中完成的任何查詢都會打開一個隱式事務(wù),防止連接返回自動提交模式)
當(dāng)在自動提交模式并且在 ?atomic()? 塊外時,函數(shù)會立即自動運行,而不會提交。
?on-commit? 函數(shù)僅適用于自動提交模式( ?autocommit mode? ),并且 ?atomic()? (或 ?ATOMIC_REQUESTS? )事務(wù)API。當(dāng)禁用自動提交并且當(dāng)前不在原子塊中時,調(diào)用 ?on_commit()? 將導(dǎo)致錯誤。

在測試中使用

Django 的 ?TestCase類將每個測試包裝在一個事務(wù)中,并在每次測試后回滾該事務(wù),以提供測試隔離。 這意味著實際上沒有任何事務(wù)被提交,因此您的 ?on_commit()? 回調(diào)將永遠不會運行。

您可以通過使用 ?TestCase.captureOnCommitCallbacks()? 來克服這個限制。 這會在列表中捕獲您的 ?on_commit()? 回調(diào),允許您對它們進行斷言,或通過調(diào)用它們來模擬事務(wù)提交。

克服限制的另一種方法是使用 ?TransactionTestCase? 而不是 ?TestCase?。 這意味著您的事務(wù)已提交,并且回調(diào)將運行。 但是 ?TransactionTestCase在測試之間刷新數(shù)據(jù)庫,這比 ?TestCase的隔離要慢得多。

為什么沒有事務(wù)回滾鉤子

事務(wù)回滾鉤子相比事務(wù)提交鉤子更難實現(xiàn),因為各種各樣的情況都可能造成隱式回滾。
比如,如果數(shù)據(jù)庫連接被刪除,因為進程被殺而沒有機會正常關(guān)閉,回滾鉤子將不會運行。
解決方法是:與其在執(zhí)行事務(wù)時(原子操作)進行某項操作,當(dāng)事務(wù)執(zhí)行失敗后再取消這項操作,不如使用 ?on_commit()? 來延遲該項操作,直到事務(wù)成功后再進行操作。畢竟事務(wù)成功后你才能確保之后的操作是有意義的。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號