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 + 线程时,
我意识到必须格外小心。