一、理解Django ORM的性能瓶颈

Django ORM是个好东西,用起来特别顺手,写两行代码就能操作数据库,完全不用操心SQL语句。但方便归方便,性能问题往往就藏在这些"顺手"的操作里。比如你可能会遇到页面加载突然变慢,或者某个接口响应时间暴涨,这时候大概率是ORM查询出了问题。

举个例子,假设我们有个博客系统,模型设计是这样的(技术栈:Django + PostgreSQL):

# models.py
class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.CharField(max_length=100)

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = models.CharField(max_length=50)

现在要获取所有文章及其作者信息,新手可能会这样写:

# 低效查询示例
posts = Post.objects.all()
for post in posts:
    print(post.title, post.author.name)  # 每次循环都查询一次作者

这就是典型的"N+1查询问题"——先查1次获取所有文章,然后每篇文章又查1次作者。如果有100篇文章,就会产生101次查询!

二、优化利器:select_related和prefetch_related

Django提供了两个神器来解决这个问题:

1. select_related:外键连表查询

# 优化后的查询
posts = Post.objects.select_related('author').all()  # 一次性关联查询作者
for post in posts:
    print(post.title, post.author.name)  # 这里不会产生新查询

这个操作相当于SQL的INNER JOIN,适合一对一或一对多关系。但要注意:

  • 不能用于多对多关系
  • 关联层级太深会导致JOIN性能下降
  • .only().defer()可以控制加载字段

2. prefetch_related:预加载多对多关系

# 处理多对多关系
posts = Post.objects.prefetch_related('tags').all()
for post in posts:
    print(post.title, [tag.name for tag in post.tags.all()])  # 预加载的tags不会产生新查询

它的原理是先执行主查询,再用额外查询获取关联数据,最后在Python层做合并。适合多对多和反向关联查询。

三、高级优化技巧

1. 使用annotate替代Python计算

比如要统计每篇文章的评论数:

from django.db.models import Count

# 低效做法
posts = Post.objects.all()
for post in posts:
    comment_count = post.comments.count()  # 每次count都查询数据库

# 高效做法
posts = Post.objects.annotate(comment_count=Count('comments'))
for post in posts:
    print(post.comment_count)  # 数据已预计算

2. 善用only和defer控制字段

# 只加载必要字段
posts = Post.objects.only('title', 'author__name')  # 连author的name也指定
# 排除大字段
posts = Post.objects.defer('content')  # 不加载文章内容

3. 批量操作代替循环

# 低效更新
for post in Post.objects.filter(created__year=2020):
    post.status = 'archived'
    post.save()  # 每次save都执行一次UPDATE

# 高效更新
Post.objects.filter(created__year=2020).update(status='archived')  # 单条SQL搞定

四、实战中的注意事项

  1. 监控先行:先用django-debug-toolbar找出问题查询
  2. 索引检查:确保外键和常用查询字段有索引
  3. 分页必加.iterator()适合大数据量,但要注意缓存问题
  4. 慎用count():大数据集下count(*)很慢,考虑缓存计数
  5. 原生SQL:复杂查询直接用raw SQL可能更高效

举个综合优化的例子:

# 复杂查询优化
from django.db.models import Prefetch

authors = Author.objects.prefetch_related(
    Prefetch(
        'post_set',
        queryset=Post.objects.select_related('category')
                  .only('title', 'created', 'category__name')
                  .annotate(comment_count=Count('comments')),
        to_attr='prefetched_posts'
    )
).filter(is_active=True)

五、不同场景的优化策略

  1. 后台管理:多用select_related + list_select_related
  2. API接口:配合drf的SerializerMethodField做定制化预加载
  3. 报表生成:考虑用.values()直接获取字典数据
  4. 定时任务:大胆用update()和bulk_create

记住:没有银弹,所有优化都要基于实际业务场景。先用工具定位问题,再针对性地优化,最后一定要验证效果。Django ORM就像汽车自动挡,方便但也要懂点"手动模式"的技巧才能开得又快又稳。