select_related适用于外键和一对一关系,走JOIN;prefetch_related支持多对多、反向关系,用额外查询+Python拼接,两者可混用但路径需严格匹配关系类型。

模型查找器的 select_related 和 prefetch_related 到底该选哪个
关系字段多、N+1 查询没察觉,是性能崩盘最隐蔽的起点。两者都为减少查询次数而生,但机制完全不同:select_related 走 JOIN,只适用于外键(ForeignKey)和一对一(OneToOneField);prefetch_related 用额外查询 + Python 端拼接,支持多对多(ManyToManyField)、反向关系、甚至自定义 QuerySet。
常见错误现象:select_related('author__profile') 看似链式写法很顺,但如果 author 是外键、profile 是 OneToOneField,它能生效;一旦中间出现多对多(比如 author__posts),Django 就静默忽略那段,不报错也不加载——结果还是 N+1。
- 用
select_related时,确保路径上全是单值关系(外键/一对一),且数据库能高效 JOIN -
prefetch_related更安全,尤其含ManyToManyField或反向related_name时必须用它 - 两者可混用:
select_related('category').prefetch_related('tags', 'comments__user') - 注意
prefetch_related默认会去重,若业务逻辑依赖重复项(如通过through模型查中间表顺序),得手动控制
filter() 里用 F 表达式还是直接写字段名
当条件涉及两个字段比较(比如 “创建时间早于更新时间”、“库存大于销量”),必须用 F;写成 filter(created_at__lt=updated_at) 是 Python 层比较,Django 会把 updated_at 当成固定值传入 SQL,结果永远查不到数据。
使用场景集中在:字段间运算、避免 Python 层取值再传回、原子性更新(配合 update())。
-
F('stock') > F('sold')→ 正确,SQL 层计算 -
F('stock') > 100→ 可以,但直接写stock__gt=100更简洁 -
F('name').lower()在 filter 中无效,Django 不支持链式方法调用,得用Lower('name')配合Q或annotate - 在
update()中用F是唯一安全方式:Product.objects.filter(id=1).update(stock=F('stock') - 1),避免竞态
什么时候该用 values() 或 values_list() 而不是 all()
如果后续只读几个字段、不做对象操作(比如没调用 save()、delete()、或访问非数据库字段),用 values() 或 values_list() 能跳过 ORM 实例化开销,内存占用直降 50% 以上——尤其查几千行时差异明显。
但别为了“看起来快”滥用:一旦需要调用模型方法、访问 @property、或后续要改数据,立刻退回 all(),否则会报 AttributeError。
-
values('id', 'title')返回字典列表,适合转 JSON 或简单渲染 -
values_list('id', 'title')返回元组列表;加flat=True(仅单字段)可得扁平列表,比如values_list('id', flat=True) - 它们绕过模型验证和信号,
save()不可用,get_absolute_url()这类方法也不存在 - 注意
values()不触发select_related,想连表字段得显式写values('author__name')
only() 和 defer() 的真实代价在哪
表面看 only() 是“只取指定字段”,defer() 是“排除某些字段”,但它们仍返回完整模型实例——这意味着所有字段的描述符、__init__ 初始化、信号钩子全在,只是未从数据库加载。实际省的只是网络传输和解析,ORM 开销几乎没减。
容易踩的坑是误以为它比 values() 更轻量,结果发现内存没降、速度没升,还因延迟加载(lazy loading)触发意外查询。
- 用
only()后首次访问未包含字段,Django 会自动发新 SQL 查询(除非已select_related过) -
defer('content')对大文本字段有意义,但若后续 90% 请求都用到content,反而增加一次查询 - 它们和
select_related/prefetch_related不冲突,但优先级高:比如only('id').select_related('author'),author 字段仍会被完整加载 - 真正省资源的方案是:确定只读、无副作用 → 用
values();需对象行为但字段少 → 先values()再用model_to_dict()手动构造轻量实例
复杂点在于,没有银弹。查 10 行和查 10 万行的优化策略可能完全相反,而 Django 的懒加载机制会让问题藏得更深——比如你以为关掉了某个字段,结果一个 @property 里又触发了整张表扫描。










