N+1 query 问题是 Django ORM 中最常见的性能问题:在循环中访问关联对象会为每个对象触发一个单独的数据库查询。select_related 和 prefetch_related 通过高效地获取关联数据来解决这个问题 — 它们是必不可少的优化工具。
N+1 问题
books = Book.objects.()
book books:
(book.author.name)
N+1 query 问题是 Django ORM 中最常见的性能问题:在循环中访问关联对象会为每个对象触发一个单独的数据库查询。select_related 和 prefetch_related 通过高效地获取关联数据来解决这个问题 — 它们是必不可少的优化工具。
books = Book.objects.()
book books:
(book.author.name)
每次访问 book.author 都会延迟地再次查询数据库。对于 100 本书,这就是 101 次查询 — 一个严重的、隐秘的性能杀手,通常只在实际数据量增加时才会显现。
# ✅ ONE query with a JOIN
books = Book.objects.select_related("author").all()
for book in books:
print(book.author.name) # NO extra queries — author was JOINed in
# 100 books → 1 query total
select_related 执行 SQL JOIN 在同一个查询中获取关联对象。用于正向 ForeignKey 和 OneToOne 关系(单个关联对象),其中 JOIN 是高效的。
# ✅ TWO queries total (not N+1)
authors = Author.objects.prefetch_related("books").all()
for author in authors:
for book in author.books.all(): # NO extra queries — books were prefetched
print(book.title)
# query 1: all authors; query 2: all their books → Django joins them in Python
prefetch_related 运行一个单独的查询来获取关联对象,并在 Python 中连接它们。用于 ManyToMany 和反向 ForeignKey 关系(多个关联对象),其中 JOIN 会低效地增加行数。
select_related → ForeignKey, OneToOne (forward, single object) → SQL JOIN, 1 query
prefetch_related → ManyToMany, reverse ForeignKey (many objects) → 2 queries, joined in Python
You can combine and chain them, and span relationships:
Book.objects.select_related("author").prefetch_related("tags")
Book.objects.select_related("author__publisher") # multi-level
✓ Django Debug Toolbar — shows the query count per page (spot N+1 visually)
✓ django-silk, or logging queries (connection.queries) to count them
→ If a page runs hundreds of similar queries, you likely have an N+1.
N+1 query 问题是 Django 应用中最常见和最具影响力的性能问题,理解 select_related/prefetch_related 对于编写高性能代码至关重要。
这个问题很隐蔽,因为便捷的 ORM 语法使访问关联对象变得如此简单(book.author.name),但它在每次访问时都会悄悄触发一个数据库查询 — 在少量记录上运行良好的代码在实际数据量增加时会性能崩溃(100 条记录 → 101 次查询),通常只在生产环境中在高负载下才被发现。
这两个工具是标准的、必不可少的解决方案:select_related 使用 SQL JOIN 在单个查询中获取正向 ForeignKey/OneToOne 关系,而 prefetch_related 使用单独的查询(在 Python 中连接)来处理 ManyToMany/反向 ForeignKey 关系,其中 JOIN 会效率低下 — 知道对哪种关系类型使用哪个是关键技能。
掌握它们可以将数百个查询转化为一两个,通常会显著改善页面性能。
这种优化是如此基础,以至于识别 N+1 问题(使用 Django Debug Toolbar 等工具来发现过多的查询次数)并应用正确的修复是区分编写高效 Django 代码的开发者与应用随数据增长而变慢的开发者的核心竞争力。
这也是最常被提问的 Django 面试话题之一,正是因为它反映了实际的、性能关键的 ORM 理解。