You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django优化:如何减少数据库查询次数?附相关代码示例

如何减少Django项目中因统计浏览量产生的数据库查询次数?

你遇到的是典型的N+1查询问题:遍历10篇文章时,每篇文章调用views_count属性都会触发一次独立的COUNT查询,额外产生10次SQL请求。下面是几种高效的解决方案:

方案1:使用annotate预聚合统计(推荐)

直接在查询Post时,用Django的聚合函数Count一次性计算出每篇文章的浏览量,把统计结果整合到初始查询中,彻底避免N+1问题。

修改views.py中的查询代码:

from django.db.models import Count

posts = Post.objects.filter(is_toplevel=True, status=Post.ACTIVE) \
    .select_related('author', 'category')  # 合并多个外键的select_related
    .prefetch_related('tags') \
    .annotate(views_count=Count('post_views_count'))  # 关联PostViews的related_name做统计

之后模板里可以直接用{{ post.views_count }},如果要保留模型中的@property,建议修改它优先使用annotate生成的字段:

@property
def views_count(self):
    # 优先用annotate的统计结果,没有的话再执行查询
    return getattr(self, 'views_count', PostViews.objects.filter(post=self).count())

方案2:给Post模型添加缓存字段

如果浏览量不需要实时精确统计(比如允许延迟更新),可以在Post模型中新增一个存储浏览量的字段,每次新增浏览记录时更新这个字段,访问时直接读取字段值,完全不需要查询关联表。

修改模型:

class Post(models.Model):
    # 原有字段...
    views_count = models.IntegerField(default=0)  # 新增缓存字段

class PostViews(models.Model):
    # 原有字段...
    def save(self, *args, **kwargs):
        is_new = self.pk is None
        super().save(*args, **kwargs)
        if is_new:
            # 仅新增浏览记录时更新对应Post的计数
            self.post.views_count = PostViews.objects.filter(post=self.post).count()
            self.post.save(update_fields=['views_count'])

或者用Django信号解耦逻辑(更优雅):

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=PostViews)
def update_post_views_cache(sender, instance, created, **kwargs):
    if created:
        # 新增记录时更新缓存字段
        instance.post.views_count = instance.post.post_views_count.count()
        instance.post.save(update_fields=['views_count'])

模板中直接使用{{ post.views_count }}即可,性能最优。

方案3:预取关联数据后计算长度

通过Prefetch对象预取所有关联的PostViews记录到内存列表,之后通过列表长度获取计数,避免重复执行COUNT查询:

修改views.py:

from django.db.models import Prefetch

posts = Post.objects.filter(is_toplevel=True, status=Post.ACTIVE) \
    .select_related('author', 'category') \
    .prefetch_related(
        'tags',
        Prefetch(
            'post_views_count',
            queryset=PostViews.objects.all(),
            to_attr='_cached_post_views'
        )
    )

修改模型中的views_count属性:

@property
def views_count(self):
    return len(getattr(self, '_cached_post_views', []))

这种方式适合需要同时获取浏览记录详情的场景,单纯统计计数的话不如方案1高效。

内容的提问来源于stack exchange,提问作者qwerty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:20:29