必须用select_related处理正向外键/一对一单值关系,用prefetch_related处理反向多对一、多对多等多值关系;二者均需在queryset上链式调用,不可在循环中访问关联字段,否则n+1问题无法解决。

直接用 select_related 或 prefetch_related,别等循环里再访问关联字段——这是唯一能真正堵住 N+1 的做法。
什么时候必须用 select_related
当你在正向 ForeignKey 或 OneToOneField 上取单值对象时,比如 book.author、order.user、profile.user。它靠 JOIN 一次性查出主表 + 关联表字段,只发 1 条 SQL。
- 支持链式:如
Book.objects.select_related('author__country'),但中间字段不能为 NULL(否则 JOIN 会丢记录) - 不支持反向关系:写
Author.objects.select_related('book_set')会被静默忽略,没报错也没效果 - 慎用在大字段上:如果
author.bio是 Text 字段且你根本不用它,JOIN 会拖慢主查询,此时改用only()限定字段更稳
什么时候必须用 prefetch_related
当你需要批量访问反向多对一(如 author.book_set.all())、多对多(如 post.tags.all())或自定义 related_name 时。它分两步查:先主表,再用 IN 批量捞关联数据,Python 层拼接。
- 关系名要写对:
ForeignKey(..., related_name='articles')就得写prefetch_related('articles'),不是article_set - 嵌套要小心:
prefetch_related('books__comments')可能触发笛卡尔积,尤其当某作者有 100 本书、每本有 50 条评论,结果集就是 5000 行 - 避免和
distinct()连用:Django 在检测到distinct后可能放弃优化,回退成 N+1;真要去重,优先用values_list('id').distinct()或 Python 层set()
怎么确认优化真的生效了
别信“写了就有效”,必须看实际 SQL。最可靠方式是打开 Django Debug Toolbar,或手动开日志:
- 在
settings.py中配置LOGGING,把django.db.backends设为DEBUG - 优化前:看到 1 条主表查询 + N 条关联查询(如
SELECT ... FROM "author" WHERE "author"."id" = 123重复出现) - 优化后:
select_related应只有 1 条含INNER JOIN的 SQL;prefetch_related应只有 2 条:1 条主表 + 1 条带WHERE "book"."author_id" IN (1,2,3...)的关联表查询
最容易被忽略的是:预加载只对 QuerySet 生效,一旦你调了 .values()、.only() 漏掉外键字段,或者用了 list(qs) 提前求值再改模型状态,select_related 就直接失效。验证必须在真实请求路径里做,不是单元测试里跑一遍就算数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











