select_related用于解决n+1查询问题,仅适用于foreignkey和onetoonefield关系,通过join一次性加载关联数据;不支持多对多、反向关系及嵌套过深场景,否则易导致性能下降。

select_related 能把 N+1 查询压成 1 次,但只对 ForeignKey 和 OneToOneField 有效,用错关系或嵌套过深反而拖慢查询。
什么时候必须用 select_related 才能解决 N+1
典型场景是循环中反复访问外键对象字段,比如:
for order in Order.objects.all():
print(order.user.username) # 每次访问都触发一次 SELECT FROM auth_user
这时 Django 默认发了 1(主表)+ N(每个 user)条 SQL。只要把 user 改成预加载即可:
-
Order.objects.select_related("user")—— 生成一条含LEFT OUTER JOIN的 SQL - 如果
User还有company外键,且你也用到了order.user.company.name,可嵌套:select_related("user__company") - 多个字段同时加载写成元组:
select_related("user", "shipping_address")
select_related 的参数和调用时机很关键
它不是万能补丁,链式调用位置和字段名稍错就失效:
- 必须在 QuerySet 求值前调用 —— 也就是不能在
list()、for循环、[0]切片之后再加select_related - 字段名必须是字符串,不支持变量或表达式:
select_related(user_field)❌,得写死"user"✅ - 反向关系(如
User查所有Order)不能用它:select_related("order_set")无效,该用prefetch_related - 多对多字段(
ManyToManyField)也不支持,哪怕定义在正向模型上
为什么有时候用了 select_related 却更慢?
JOIN 不是银弹,膨胀和限制常被忽略:
- 关联表行数远大于主表时(比如 1 个用户对应 10 万条日志),JOIN 会复制主表数据,结果集体积暴增,内存和网络开销反而更大
- MySQL 默认最多 JOIN 61 张表,嵌套到
"a__b__c__d__e__f__g"这种层级容易直接报ERROR 1116 (HY000): Too many tables - SQLite 对复杂 JOIN 的优化能力弱,开发环境看着快,上线后可能卡顿
- 如果只用关联对象的 1–2 个字段,却把整张大表全 JOIN 进来,属于典型的数据过载 —— 此时应搭配
only(),例如:select_related("user").only("id", "status", "user__username")
怎么确认 select_related 真生效了?
别信直觉,看实际 SQL 和查询次数:
- 开启 Django DEBUG 模式后,检查
django.db.connection.queries,对比加/不加select_related的 SQL 条数和内容 - 用
str(QuerySet.query)打印最终生成的 SQL,确认是否含LEFT OUTER JOIN及对应 ON 条件 - 注意:如果后续又用了
filter()带关联字段条件(如.filter(user__is_active=True)),Django 仍会 JOIN,但逻辑变成 WHERE 约束,不是单纯加载 —— 这没问题,但别误以为它没起作用
最常被跳过的点是:嵌套深度和字段选择权衡。宁可拆成两个带 prefetch_related 的查询,也别硬塞五层 select_related 让数据库扛不住。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











