Django ORM中的N+1问题,为什么总是被提起?
使用Django ORM时,我们常常会听到“N+1问题”。但是如果没有亲身经历,很难感受到这个问题的严重性。
简单来说,这就是“原本只需要执行一次的查询却被执行得远超预期的问题”。这样一来,页面加载速度可能会显著变慢,数据越多,性能下降会越严重。尤其在查询模型间有关联的数据时,这个问题尤为常见,因此使用ORM时必须理解这一点。
💡 N+1问题,简单理解一下
假设我们要创建一个博客系统。每个作者(Author)可以撰写多个文章(Post)。用Django模型表达如下。
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE)
每个作者(Author)可以撰写多个文章(Post),因此关系为1:N。
现在假设我们要实现一个功能,输出所有作者的名字和他们撰写的文章标题。通常可以这样写:
⚠️ 发生N+1问题的代码
authors = Author.objects.all() # (1) 第一个查询 (查询Author表)
for author in authors:
print(author.post_set.all()) # (N) 独立查询每个Author的Post
执行一次后,加载时间可能会比预期更长。如果数据不多,可能不会显现出来,但如果作者有100名,文章超过500篇呢?问题就会开始显现。
🧐 实际执行的SQL查询
SELECT * FROM author; -- 第1次执行
SELECT * FROM post WHERE author_id = 1; -- 执行N次
SELECT * FROM post WHERE author_id = 2;
SELECT * FROM post WHERE author_id = 3;
...
随着作者数量的增加,查询的次数会呈几何级数增长。这就是N+1问题。
🚀 N+1问题常常在什么情况下发生?
实际上,是否熟练使用Django ORM都无关紧要,查询两个或以上模型相关数据时,几乎所有情况下都必须注意这个问题。尤其在以下情况下常会发生:
1️⃣ 在模板的循环中引用数据的情况
{% for author in authors %}
{{ author.name }}
{% for post in author.post_set.all %}
- {{ post.title }}
{% endfor %}
{% endfor %}
在模板中使用循环访问数据时,每个对象都会产生额外的查询,从而导致性能下降。
2️⃣ 在视图函数中循环内引用相关模型
def blog_view(request):
authors = Author.objects.all() # 第一次查询 (查询Author)
author_list = []
for author in authors:
author_list.append({
'name': author.name,
'posts': author.post_set.all() # 额外产生N个查询
})
return render(request, 'blog.html', {'authors': author_list})
在视图函数而非模板中处理数据时,同样会出现此问题。
💡 亲身经历过的N+1问题
以前我创建过一个名为get_absolute_url()
的自定义方法。这个方法通过组合模型的多个字段来生成URL,但在性能下降的问题出现后,我才找到原因。
class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def get_absolute_url(self):
return f"/author/{self.author.name}/{self.slug}/"
每次调用这个方法时,self.author.name
都会被执行,产生N个额外的查询。解决这个问题花了我相当长的时间。
✅ 总结:必须理解N+1问题的原因
- 只要使用Django ORM,这个问题就可能随时发生。
- 如果页面速度变慢,ORM可能正在执行比预期更多的查询。
- 可能在模板、视图函数、模型方法的任何地方发生。
- 数据越多,性能下降越严重。
- ORM不会自动进行优化,因此需要手动检查执行的查询并进行优化。
✅ 在下一篇文章中,将讨论如何使用select_related
和prefetch_related
解决N+1问题。
댓글이 없습니다.