Django+DRF+Celery:使用on_commit后仍出现ModelDoesNotExistError
问题分析与解决
你遇到的问题是:在Django事务中通过transaction.on_commit()触发Celery任务,但任务执行时依然出现ModelDoesNotExistError,明明已经在事务内保存了模型实例,任务却查不到。以下是具体原因和解决办法:
可能原因1:嵌套事务导致on_commit未及时触发
如果你的ViewSet运行在全局事务上下文(比如开启了TransactionMiddleware中间件),手动添加的with transaction.atomic()会变成嵌套事务。此时on_commit绑定的回调只会在最外层事务提交时执行,若外层事务延迟提交或未提交,任务就会在实例未真正写入数据库时启动。
解决办法
- 检查项目是否启用了全局事务中间件,若业务不需要可直接关闭;
- 若必须使用嵌套事务,可通过
transaction.get_connection().in_atomic_block判断当前事务层级,确保on_commit绑定到正确的事务上下文。
可能原因2:闭包延迟绑定陷阱
Lambda表达式是延迟求值的,极端情况下如果m对象在事务结束前被意外修改,可能导致传入任务的m.id出现异常(虽然你的代码中概率较低,但可以提前规避)。
解决办法
提前捕获m.id的值,避免闭包的延迟绑定:
# 替换原lambda写法 task_operation_id = m.id transaction.on_commit(lambda: my_fav_task.delay(task_operation_id, param2, param3))
可能原因3:数据库读写分离导致数据同步延迟
如果项目使用了读写分离架构,Celery任务默认连接从库,而主库的事务提交后,从库可能存在短暂的同步延迟,此时任务查询从库就会找不到刚创建的实例。
解决办法
- 强制任务使用主库连接查询:
@shared_task def my_fav_task(operation_id, **params): # 假设default是主库配置 operation = MyModel.objects.using('default').get(id=operation_id) # 后续业务逻辑
- 给任务添加重试机制,应对短暂的同步延迟:
from celery.exceptions import Retry @shared_task(bind=True, max_retries=3) def my_fav_task(self, operation_id, **params): try: operation = MyModel.objects.get(id=operation_id) except MyModel.DoesNotExist: self.retry(countdown=1) # 后续业务逻辑
可能原因4:模型save方法重写导致未持久化
如果MyModel重写了save方法,但未调用父类的save(),会导致实例没有真正写入数据库,自然会出现查询不到的情况。
解决办法
检查MyModel的save方法,确保调用了父类方法完成持久化:
class MyModel(models.Model): # 字段定义... def save(self, *args, **kwargs): # 自定义业务逻辑 super().save(*args, **kwargs) # 必须调用此方法完成数据库写入
内容的提问来源于stack exchange,提问作者Jedi Knight
相关产品推荐
相关产品推荐

