必须用select_related处理外键或一对一字段的循环访问,用prefetch_related处理反向关系或多对多字段;二者均需在QuerySet中显式声明,否则触发N+1查询。

直接用 select_related 或 prefetch_related,别等出问题再查——90% 的 N+1 问题都源于没在 QuerySet 上提前声明关联预取。
什么时候必须用 select_related
当你访问的是外键(ForeignKey)或一对一(OneToOneField)字段,且该字段在循环中被反复读取时,select_related 是唯一正确解法。
- 典型错误:遍历
Book.objects.all(),然后对每个book.author.name取值 → 每本书触发一次作者查询 - 正确写法:
Book.objects.select_related('author'),SQL 自动加LEFT JOIN author ON book.author_id = author.id - 支持多层:如需作者的国家,写
select_related('author__country')即可,但注意层级过深会拖慢 JOIN 性能 - 不能用于反向多对一(比如
Author.books.all()),那是prefetch_related的职责
什么时候必须用 prefetch_related
当你需要访问反向关系(如 author.books.all())或多对多字段(如 article.tags.all())时,prefetch_related 才是安全选择;它不走 JOIN,而是发两条独立 SQL,在 Python 层拼接。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 错误示范:
for author in Author.objects.all(): print(author.books.count())→ 每个作者查一次books表 - 正确写法:
Author.objects.prefetch_related('books')→ 先查所有作者,再查所有关联书籍,内存里匹配 - 可预过滤:用
Prefetch('books', queryset=Book.objects.filter(is_published=True))避免加载无用数据 - 慎用
to_attr:如to_attr='published_books'会把结果存为列表属性,后续不能再链式调用.filter()
容易被忽略的三个坑
很多人写了 select_related 还是慢,不是 ORM 不行,而是踩了这些隐性陷阱:
立即学习“Python免费学习笔记(深入)”;
-
select_related对字段名大小写敏感,写成'Author'(首字母大写)就完全失效,不报错也不生效 - 在
.values()或.values_list()后调用select_related会被静默忽略——因为字段投影后已无对象实例可挂载关联数据 - 模板里访问
{{ book.author.name }}看似简单,但如果视图没加select_related,Django 就真会现场查库,且 debug toolbar 很难一眼定位到这一行
最危险的情况是:你本地数据少,N+1 看不出延迟;一上生产,几百条记录就把数据库连接池打满。别依赖“好像没出事”,只要模型间有外键或反向关系,QuerySet 就得明确声明怎么加载。

















