Django相同filter重复查询是否自动优化及N+1问题排查
Django模型查询逻辑问题解答
涉及的原始模型代码
class MyModel(models.Model): task_id = models.UUIDField() task_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name='+') task = GenericForeignKey('task_type', 'task_id') ..... @property def task_description(self): if self.task_status === 'pending': return 'Pending Task' else: task = Task.objects.filter(self.task_id).first() return task.name or ''
代码前置说明:原始代码存在两处语法错误,以下回答均基于修正后可正常运行的逻辑展开:
- Python相等判断使用双等号
==,不存在三等号===语法filter查询需明确指定字段,正确写法为Task.objects.filter(id=self.task_id).first()
问题答复
1. 待处理task id列表为[1,2,3,3,3]时,实际发起的数据库查询情况
如果是遍历5个关联上述id的MyModel实例、逐个访问task_description属性,且所有实例的task_status均不为pending,默认无优化的场景下会向Task表发起5次独立查询,对应SQL的查询条件依次为:
WHERE id = 1 LIMIT 1WHERE id = 2 LIMIT 1WHERE id = 3 LIMIT 1WHERE id = 3 LIMIT 1WHERE id = 3 LIMIT 1
如果部分实例task_status为pending,对应实例不会触发Task表查询;如果提前配置了预加载或手动实现了缓存,查询次数会对应减少。
2. 该实现是否属于N+1查询问题
属于典型的N+1查询问题。
N+1查询的核心特征是:先执行1次查询获取N条主表(此处为MyModel)数据,遍历每条主表数据时,再单独发起1次关联表(此处为Task)查询,总查询次数为1+N。
该实现没有对关联Task数据做预加载,每个非pending状态的MyModel实例访问属性时都会单独发起一次Task查询,完全符合N+1问题的特征,数据量较大时会产生大量冗余数据库请求,拖慢接口响应速度。
3. 重复出现的id=3是否会重复执行3次WHERE id = 3的查询
默认配置下会重复执行3次。
Django ORM默认不会自动缓存属性方法内手动编写的filter查询:QuerySet本身是惰性求值,但每次在属性中调用Task.objects.filter(...)都会生成一个全新的QuerySet实例,没有跨QuerySet的结果复用逻辑,哪怕查询条件完全一致,也会重新向数据库发起请求。
如果要避免重复查询,可以通过两种方式优化:
- 给属性增加实例级缓存,第一次查询到结果后存在实例私有属性中,后续访问直接返回缓存值
- 初始查询MyModel列表时,使用
prefetch_related提前批量拉取关联的Task数据,属性逻辑优先读取预加载的缓存结果
内容的提问来源于stack exchange,提问作者Mamoon Ahmed
相关产品推荐
相关产品推荐

