select_related仅适用于foreignkey和onetoonefield,通过left join预取单值关联;prefetch_related专用于多值关系(如manytomanyfield、反向foreignkey),通过额外查询+python归组实现;values()会禁用所有预取,应改用only()/defer()。

select_related 只能用于 ForeignKey 和 OneToOneField
它靠 SQL 的 LEFT OUTER JOIN 一次性把关联字段拉回来,所以必须是“一个主对象对应一个关联对象”的关系。比如 Order.user、User.profile 这类单值路径。
常见错误现象:
-
select_related("tags")对ManyToManyField报FieldError: Invalid field name given in select_related -
select_related("comment_set")对反向多对一也直接失败,因为comment_set是集合,不是单个对象
性能影响:JOIN 深度太大(如 "a__b__c__d__e__f__g")可能触发 MySQL 的 ERROR 1116: Too many tables;另外,NULL 关联会导致后续访问 obj.author.profile.name 时抛 AttributeError,得手动判空。
prefetch_related 是为集合关系设计的
它不走 JOIN,而是发额外查询 + Python 层归组,专治 ManyToManyField、反向 ForeignKey(如 user.order_set)、自定义 related_name 这类“一对多”场景。
使用要点:
- 必须实际访问集合内容才会触发预取,比如调用
post.tags.all()或len(post.comment_set.all());只写prefetch_related("tags")但没访问,等于白配 - 支持
Prefetch对预取结果加过滤:Prefetch("comments", queryset=Comment.objects.filter(is_public=True)) - 不能和同字段的
select_related混用——比如已select_related("author"),再加prefetch_related("author__profile"),后者会被静默忽略
values() 会彻底废掉所有预取逻辑
一旦在 QuerySet 上调了 .values("title", "author_id"),返回的就是字典列表,不再是模型实例。之后再访问 obj.author.name 就又变回 N+1 查询。
这是因为预取依赖对象实例的内部缓存机制(_state 和 _prefetched_objects_cache),而 values() 绕过了整个 ORM 实例化流程。
替代方案:
- 用
only("title", "author_id")或defer("content", "body")控制字段加载,保留模型实例 - 真要字典格式,就别指望预取,老老实实自己 join 或 annotate
二者可以组合,但顺序和字段不能冲突
像 Order.objects.select_related("customer").prefetch_related("items__product") 是合法的:前者处理 customer(单值),后者处理 items(多值)及其嵌套的 product(单值,但属于多值集合的子项)。
容易踩的坑:
- 误以为
prefetch_related("items__product")能替代select_related("items__product")—— 实际上items__product是“多对一中的多对一”,Django 允许 prefetch 嵌套,但底层仍是两次查询 + 归组,不是 JOIN - 在同一个 QuerySet 上对同一字段既
select_related又prefetch_related,后者失效且无提示 - 过度嵌套
prefetch_related("a__b__c__d")导致内存暴涨,尤其当某层返回几千条记录时,Python 归组开销远超数据库 JOIN
最常被忽略的一点:预取是否生效,不看代码写了没,而要看视图里有没有真正触发集合迭代或计数。很多慢查询修复后依然没变快,就是因为漏掉了这一步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











