Django中外键对象获取:prefetch_related与model_set对比及性能分析
在Django中获取外键关联对象的最佳实践:性能与流程对比
结论先行:在视图层使用prefetch_related预取关联对象是性能更优的方案,能彻底避免模板直接调用model_set.all()带来的N+1查询性能问题。
两种方式的核心差异
1. 模板中直接调用model_set.all():性能陷阱
当你在模板里循环父对象并逐个调用object.book_set.all()时,Django会为每个父对象单独发起一次SQL查询。比如你有100个父对象,就会先执行1次查询获取所有父对象,再执行100次查询分别获取每个父对象的关联数据——这就是典型的N+1查询问题,父对象数量越多,数据库查询次数暴增,页面响应速度会急剧下降。
2. 视图层使用prefetch_related:高效预取
prefetch_related的工作逻辑是:
- 先执行1次SQL查询获取所有父对象;
- 再执行1次SQL查询获取所有与这些父对象关联的子对象;
- 最后在Python内存层面自动将父对象和子对象关联起来。
无论父对象有多少个,总共只需要2次SQL查询,能极大降低数据库负载,提升页面性能。
代码示例对比
假设我们有以下模型:
class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='book_set')
不推荐的写法(模板直接调用)
views.py:
def author_list(request): authors = Author.objects.all() return render(request, 'author_list.html', {'authors': authors})
模板author_list.html:
{% for author in authors %} <h3>{{ author.name }}</h3> {% for book in author.book_set.all %} <p>{{ book.title }}</p> {% endfor %} {% endfor %}
若有100个作者,会产生101次SQL查询,性能极低。
推荐的写法(视图层预取)
views.py:
def author_list(request): authors = Author.objects.all().prefetch_related('book_set') return render(request, 'author_list.html', {'authors': authors})
模板代码和上面完全一致,但此时仅需2次SQL查询,性能提升显著。
补充说明
- 如果是正向外键关联(比如从
Book查询对应的Author),应该使用select_related,它通过SQL JOIN语句一次性将主表和关联表的数据查询出来,比prefetch_related更高效。 - 模板直接调用的方式仅适合关联对象极少的测试场景,生产环境务必避免。
内容的提问来源于stack exchange,提问作者Zainab Imad
相关产品推荐
相关产品推荐

