1. 問題情況:on_commit() 會不會執行得太快?

最近在 Django 專案中使用 transaction.on_commit() 設定
讓 Celery 任務在交易成功後執行。

但是,查看日誌時,發現了一個奇怪的現象。

即使尚未完成像 post.categories.add(...) 的操作,
on_commit() 裡的 Celery 任務卻已經執行了。


2. 查看代碼…

def view():
    post = Post.objects.create(...)

    def post_process():
        with transaction.atomic():
            post.categories.add(...)
            post.tags.add(...)

            def run_async():
                translate_post.delay(post.id)

            transaction.on_commit(run_async)

    threading.Thread(target=post_process).start()

起初看似沒問題…
問題在於這個 threading.Thread() 的使用。


3. 原因:交易是執行緒本地的

Django 的交易依賴於當前執行緒。
也就是說, transaction.atomic()on_commit()應用的僅限於當前執行緒打開的交易

在新的執行緒中打開的 transaction.atomic()
與主交易毫無關聯。


4. 最終發生了什麼

  • Celery 任務雖然在“交易提交之後”執行,但
  • 這個交易只是在子執行緒中的小交易
  • post.categories.add(...) 等操作仍在進行中

換句話說,Celery 的執行比預期要快
因此產生了參考不完整數據的問題。


5. 解決方法

只需這樣修改即可:

def view():
    post = Post.objects.create(...)

    with transaction.atomic():
        post.categories.add(...)
        post.tags.add(...)

        def run_async():
            translate_post.delay(post.id)

        transaction.on_commit(run_async)

🚫 threading.Thread(...)
✅ 在主執行緒中註冊 on_commit()


6. 總結

  • Django 的交易是 執行緒本地的
  • on_commit()僅在當前執行緒的交易提交之後執行
  • 在其他執行緒中註冊,僅響應該執行緒的交易

這次經驗讓我獲得了強有力的教訓。
在使用交易 + Celery + 執行緒時,
我深刻體會到必須小心。