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

2022-03-16 17:37 更新

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

Django 提供了 on_commit() 函數(shù)來注冊(cè)在事務(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)用。
無任何活動(dòng)事務(wù)時(shí)調(diào)用 ?on_commit()? ,則回調(diào)函數(shù)會(huì)立即執(zhí)行。
如果假設(shè)的數(shù)據(jù)庫寫入被回滾(尤其是在 ?atomic()? 塊里引發(fā)了一個(gè)未處理異常),函數(shù)將被丟棄且永遠(yuǎn)不會(huì)被調(diào)用。

保存點(diǎn)

正確處理保存點(diǎn)(即嵌套了 ?atomic()? 塊)。也就是說,注冊(cè)在保存點(diǎn)后的 ?on_commit()?  的調(diào)用(嵌套在 ?atomic()? 塊)將在外部事務(wù)被提交之后調(diào)用,但如果在事務(wù)期間回滾到保存點(diǎn)或任何之前的保存點(diǎn)之前,則不會(huì)調(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)保存點(diǎn)回滾時(shí)(因引發(fā)異常),內(nèi)部調(diào)用不會(huì)被調(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)初注冊(cè)時(shí)的順序一致。

異常處理

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

執(zhí)行時(shí)間

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

在測(cè)試中使用

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

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

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

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

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


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)