n+1查询指主查询执行1次后,每条结果触发1次关联查询,共n+1次sql;django orm因默认惰性加载反向关系或外键字段而易触发,如循环访问book.author.name导致100本书引发100次额外查询。

什么是N+1查询,为什么Django ORM容易触发它
Django ORM在默认惰性加载(lazy loading)机制下,对反向关系或外键关联字段不做预加载。比如 Book.objects.all() 查出100本书,再循环访问每本的 book.author.name,就会额外触发100次SQL查询——主查1次 + 关联查100次 = N+1。这不是Bug,是设计使然;但没意识地写,性能会断崖式下跌。
常见触发场景包括:
- 在模板中用
{{ book.author.name }}渲染外键字段 - 视图里循环调用
obj.foreign_key.field - 使用
values()或values_list()后又访问对象属性
用 select_related() 解决一对一/外键正向查询
select_related() 生成 SQL 的 JOIN,把关联表字段一次性查出来,适合正向关系(外键、OneToOneField)。
示例:查所有书及其作者姓名
books = Book.objects.select_related('author').all()
for book in books:
print(book.author.name) # 不再触发额外查询
注意点:
- 只支持正向关系,不能用于
ForeignKey的反向(如Author.books.all()) - 多层嵌套可用双下划线:
select_related('author__profile') - 如果关联字段可能为
None(外键允许为空),Django 默认用INNER JOIN,会丢数据;加nullable=True参数不生效,得改用LEFT OUTER JOIN—— 实际要靠select_related()自动处理,只要字段定义了null=True,Django 就会用LEFT JOIN
用 prefetch_related() 解决多对一/多对多反向查询
prefetch_related() 用“分两步查询 + Python端拼接”策略,适合反向关系(ForeignKey 反向、ManyToManyField、GenericRelation)。
示例:查所有作者及其全部书籍
authors = Author.objects.prefetch_related('book_set').all()
for author in authors:
for book in author.book_set.all(): # 不再N次查询
print(book.title)
关键细节:
- 底层执行两条SQL:先查
Author,再用IN批量查所有关联Book(带WHERE author_id IN (…)) - 支持嵌套:
prefetch_related('book_set__tags') - 若只想取部分字段,用
Prefetch控制:Prefetch('book_set', queryset=Book.objects.only('id', 'title')) - 对
ManyToManyField,即使没显式使用中间表,也必须用prefetch_related,select_related完全无效
什么时候该用 only() / defer() 配合优化
select_related 和 prefetch_related 解决的是“查多少张表”,而 only() 和 defer() 解决的是“每张表查哪些字段”。
比如你只需要书名和作者名,却默认加载了 Book 的全部20个字段(含大文本、JSON字段),内存和网络开销就浪费了。
推荐组合:
select_related('author').only('title', 'author__name')prefetch_related('book_set').only('name').prefetch_related(Prefetch('book_set', queryset=Book.objects.only('title', 'isbn')))
注意:
-
only()列表里没包含的字段,首次访问时会触发懒加载(又回数据库),所以务必确保字段覆盖完整使用路径 -
defer()是反向操作,但容易误判,新手建议优先用only()
Django 5 没改变这些机制的本质逻辑,但对 Prefetch 的嵌套解析更健壮,也支持在 annotate() 后继续 prefetch_related。真正容易被忽略的,是模板里看似无害的一行 {{ obj.related_field.name }} —— 它不会报错,只会悄悄拖慢整个列表页。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











