Django后台保存Post时Celery任务查询不到Post的问题
解决Django Admin保存Post后Celery任务找不到对象的问题
核心问题分析
你遇到的问题本质是事务提交时机与Celery任务执行时机不匹配,即使使用了transaction.on_commit,大概率是用法有误,或者存在事务/数据库配置层面的隐藏问题。以下是具体的排查和解决步骤:
1. 修正transaction.on_commit的用法
很多时候问题出在on_commit的包裹范围不对,必须把Celery任务的**异步调用(delay/apply_async)**放在on_commit的回调里,且要在super().save_model之后执行:
# admin.py from django.db import transaction from django.contrib import admin from .models import Post from .tasks import perform_task class PostAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): # 先处理元数据逻辑 obj.meta_data = "处理后的元数据内容" # 调用父类方法完成数据库写入(这一步会触发事务) super().save_model(request, obj, form, change) # 仅当外层事务提交后,才触发Celery异步任务 transaction.on_commit(lambda: perform_task.delay(obj.id))
注意:不要把任务的同步执行逻辑放在on_commit里,必须是异步调用;直接传ID比传对象更安全,避免变量捕获导致的属性不一致问题。
2. 给Celery任务添加自动重试机制
即使事务提交,也可能因为数据库主从同步延迟、网络波动等原因导致任务执行时找不到对象。给任务加自动重试可以解决这类偶发问题:
# tasks.py from celery import shared_task from django.http import Http404 from .models import Post @shared_task(bind=True, autoretry_for=(Http404, Post.DoesNotExist), retry_backoff=2, retry_kwargs={"max_retries": 3}) def perform_task(self, post_id): try: # 如果用了读写分离,强制使用写库避免主从延迟问题 post = Post.objects.using("default").get(id=post_id) print(f"Celery端获取到Post标题:{post.title}") # 生成嵌入向量的逻辑 post.embedding = generate_embedding(post.content) post.save(update_fields=["embedding"]) except (Post.DoesNotExist, Http404): # 触发重试 raise self.retry(exc=Http404(f"Post {post_id} 暂未找到,重试中..."))
3. 检查数据库隔离级别与读写分离配置
- 如果使用MySQL,默认的
REPEATABLE READ隔离级别可能导致Celery worker的事务看不到刚提交的Post,可以临时在任务里切换隔离级别:from django.db import transaction def perform_task(self, post_id): with transaction.atomic(): transaction.set_isolation_level(transaction.READ_COMMITTED) post = Post.objects.get(id=post_id) - 若配置了读写分离,Celery worker可能连接的是读库,而写库的变更还未同步到读库,此时需要强制任务使用写库(如上面代码中的
using("default"))。
4. 排查Admin中的嵌套事务
如果你的save_model里手动开启了嵌套事务(transaction.atomic()),transaction.on_commit只会在最外层事务提交时触发。可以通过以下代码确认当前事务状态:
from django.db import transaction def save_model(self, request, obj, form, change): print(transaction.get_connection().in_atomic_block()) # 输出True表示在事务中 # ... 其他逻辑
如果存在嵌套事务,需要调整代码结构,确保on_commit绑定到正确的事务层级。
内容的提问来源于stack exchange,提问作者Talha Anwar
相关产品推荐
相关产品推荐

