如何在Python Django 4.2中优化慢查询_使用select_related与prefetch_related

陌婷小哥_3429

陌婷小哥_3429

2026-05-27

234人浏览

原创

select_related 仅适用于 foreignkey 和 onetoonefield,通过 join 优化单值关联;prefetch_related 用于多值关联(如 manytomanyfield、反向关系),通过额外查询+python归组实现优化;二者必须在 queryset 构建阶段正确选用,否则无效甚至恶化性能。

如何在python django 4.2中优化慢查询_使用select_related与prefetch_related

直接说结论:select_related 和 prefetch_related 不是“用了就快”,而是必须匹配关联类型、访问模式和数据规模才真正生效;用错一个,不仅没优化,还可能让查询更慢、内存更高、甚至报错。

什么时候该用 select_related 而不是 prefetch_related

select_related 只对单值关系有效:即 ForeignKey(正向)和 OneToOneField。它靠 SQL 的 LEFT OUTER JOIN 一次性把关联表字段拉回来。

  • ✅ 正确场景:查 Order 列表时要显示每个订单的 user.username 和 user.company.name —— 全是外键链,写成 Order.objects.select_related("user__company")
  • ❌ 错误场景:想查 Post 的所有 tags(ManyToManyField)或 post.comment_set.all()(反向多对一),select_related("tags") 会直接抛 FieldError
  • ⚠️ 注意 NULL:JOIN 后字段可能为 NULL,比如 user.profile 不存在时,obj.user.profile.bio 会报 AttributeError,得先判空
  • ⚠️ 嵌套深度有硬限制:MySQL 默认最多 JOIN 61 张表,"a__b__c__d__e__f__g" 这种七层嵌套在某些部署环境下会触发 ERROR 1116 (HY000): Too many tables

什么时候必须换用 prefetch_related

prefetch_related 是为集合关系设计的:反向多对一(如 user.order_set)、多对多(如 post.tags)、自定义 related_name 的任意集合字段。它发额外查询 + Python 层归组,不走 JOIN。

Claw Code Suite (Python-Only Edition)
Claw Code Suite (Python-Only Edition)

仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...

下载
  • ✅ 正确场景:展示文章列表时要渲染每篇的 tags.all 和 comments.count,写成 Post.objects.prefetch_related("tags", "comment_set")
  • ❌ 白跑情况:只写了 prefetch_related("comments"),但在视图里没调 post.comments.all() 或 len(post.comments.all()) —— 预取根本不会触发
  • ⚠️ 不能混用同一字段:比如已 select_related("author"),再加 prefetch_related("author__profile"),Django 会静默忽略后者
  • ⚠️ values() 会废掉所有预取:一旦调用 .values("title", "author_id"),后续访问 obj.author.name 就又变 N+1 —— 因为返回的是字典,不是模型实例

模板和序列化里最容易漏掉的 N+1

N+1 很少出现在 view 函数的 for 循环里,更多藏在 DRF 序列化器或 Django 模板中,而且你根本看不到 SQL 发起点。

  • DRF 的 SerializerMethodField:如果方法里写了 obj.author.name,但 QuerySet 没 select_related("author"),每个对象都会单独查一次 author
  • 模板中 {{ post.author.name }} 看似简单,但若 post 是从 Post.objects.all() 来的,就是典型 N+1
  • 反向关系名必须严格匹配:prefetch_related("comment_set") 有效,但模板里写 {% for c in post.comments.all %} 就失效——除非模型定义了 related_name="comments"
  • count() 是查询,不是属性缓存:哪怕用了 prefetch_related("comments"),post.comments.count() 仍会发新 SQL,应改用 post.comments.all().count()(已预取)或提前注解 .annotate(comments_count=Count("comments"))

性能权衡:JOIN 快还是两次查询快?

没有银弹。JOIN 并不总是更快——尤其当关联表行数远超主表、或你只用关联表一两个字段时,select_related 可能导致结果集膨胀、传输变慢、内存飙升。

  • 主表 100 行 × 关联表平均 50 行 → JOIN 后传 5000 行数据,而 prefetch_related 只传 100 + 50 = 150 行,明显后者更轻量
  • 你要排序或过滤关联字段(如 .order_by("user__last_login")),select_related 更合适;但若要按关联表聚合(如 .annotate(avg_rating=Avg("review__score"))),JOIN 可能生成低效 SQL,此时先 prefetch_related 再 Python 计算反而更稳
  • 别跳过基础优化:索引比任何 ORM 技巧都重要。没给 user_id 加索引,select_related("user") 再快也卡在 JOIN 扫表上

最常被忽略的一点:这两个方法都依赖 QuerySet 的惰性求值。一旦你调了 list()、len()、[0]、或者在模板里用了 {% for %},QuerySet 就执行了——之后再链式加 select_related 完全无效。优化必须写在 QuerySet 构建阶段,而不是数据拿到手之后补救。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1771

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

4584

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1729

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

27057

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3267

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3287

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1203

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

596

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2543

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Uvicorn官方安装与入门手册
Uvicorn官方安装与入门手册

共0课时 | 0人学习

Python uv官方项目管理指南
Python uv官方项目管理指南

共0课时 | 0人学习

Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习