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