Django模型关联属性访问是否重复查询数据库及优化方法
问题1:关联属性访问的查询行为与优化方案
默认写法的查询触发逻辑
访问self.xyz.bar.name与self.xyz.bar.text会触发额外数据库查询,具体触发规则遵循Django ORM的懒加载+实例缓存机制:
- 若加载
Foo实例时未预取关联,第一次访问self.xyz会触发1次针对Xyz表的查询 - 拿到
Xyz实例后,第一次访问xyz.bar会触发1次针对Bar表的查询 - 当
bar实例被加载到Xyz实例的内存缓存后,后续再访问bar.name、bar.text不会再触发新的数据库查询
也就是说,两个属性连续访问的最坏情况是触发2次额外查询,不会出现访问两个字段查两次Bar表的情况。
单方法select_related写法的效果
你写的每个方法单独调用Xyz.objects.select_related('bar').get()的写法没有优化效果,反而会降低性能:
- 每调用一次
bar_name()或bar_text(),都会独立执行一次带JOIN的SQL查询 - 如果两个方法同时调用,会触发2次完全重复的数据库查询,比默认懒加载的性能还差。
正确优化方案
性能最优的方案是在查询Foo的源头就通过select_related预加载整条关联链,示例:
# 查单个Foo时 foo = Foo.objects.select_related('xyz__bar').get(pk=foo_id) # 查Foo列表时 foos = Foo.objects.select_related('xyz__bar').all()
用这种方式拿到的Foo实例,后续访问self.xyz.bar.name、self.xyz.bar.text时不会触发任何额外查询——所有关联数据会在第一次查询Foo时通过SQL JOIN一次性取出,由ORM自动填充到关联实例缓存中,不需要写任何额外的实例方法。
问题2:类Singleton关联获取方法的可行性
你写的singleton_bar方法完全不可行,既不是单例,也起不到缓存优化作用:
- 每次调用
self.singleton_bar()都会重新执行Xyz.objects.select_related('bar').get(pk=self.xyz_id),也就是每次都会发新的数据库查询 - 如果连续调用
bar_name()和bar_text(),会触发2次重复SQL,性能远差于Django默认的懒加载机制(默认懒加载至少会在第一次拿到关联对象后缓存到实例内存,后续访问不查库)。
如果要在实例方法层面做关联对象缓存(仅作为源头未预加载时的兜底优化),正确写法是把查询结果挂载到实例私有属性上,仅第一次访问时查库,后续直接返回缓存值:
def _cached_bar(self): if not hasattr(self, '_bar_cache'): self._bar_cache = self.xyz.bar return self._bar_cache def bar_name(self): return self._cached_bar().name def bar_text(self): return self._cached_bar().text
注意这种实例级缓存的优化收益非常有限,优先使用源头select_related('xyz__bar')的方案才是正解。
额外提示:你贴的模型代码存在两处笔误,实际运行会报错:
CharField的参数名是max_length不是max_lenth,外键字段名是ForeignKey不是ForeingKey。
内容的提问来源于stack exchange,提问作者Humberto Vieira
相关产品推荐
相关产品推荐

