You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 02:36:18