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

如何优化Django Rest Framework序列化器查询以提升响应速度

Django DRF 序列化器多对多关联查询的性能优化

你的代码主要问题是循环执行数据库查询(N+1问题)、冗余的关联数据查询、低效的模糊查询这几点导致响应慢,下面是针对性的优化方案:


一、优化数据库查询次数与关联预取

1. 预取Search模型的关联数据

原代码中obj.search_tags.select_related('component')是多余的(因为search的component就是当前obj),换成预取channel和channel__category,避免后续查询YTVideo时触发额外的DB请求:

searches = obj.search_tags.filter(is_active=True).select_related('channel', 'channel__category')

原循环中videos.select_related('channel')是重复操作,前面查询时已经加过select_related('channel'),直接移除即可。


二、优化查询条件与模糊查询效率

1. 提前处理标签并过滤空值

避免在查询时重复拆分字符串,提前处理标签并过滤空值,减少无效查询:

tags = [tag.strip() for tag in search.content.split(',') if tag.strip()]
if not tags:
    continue  # 无有效标签时跳过查询

2. 用外键替代名称查询(如果适用)

如果Search模型的category是外键关联Category模型,把Q(channel__category__name=search.category)改成Q(channel__category=search.category),外键查询依赖索引,比字符串匹配快很多。

3. 替换低效的icontains为全文搜索

title__icontains是无索引的模糊查询,数据量大时极慢。如果用PostgreSQL,改用全文搜索提升速度:

from django.contrib.postgres.search import SearchVector, SearchQuery

# 构建全文搜索查询
search_query = SearchQuery(' | '.join(tags), search_type='websearch')
videos = YTVideo.objects.select_related('channel').annotate(
    search=SearchVector('title')
).filter(
    (Q(channel=search.channel) | Q(channel__category=search.category)) & Q(search=search_query)
).order_by(search.ordering_field)[:search.limit]

同时给YTVideo的title字段加GIN索引:

class YTVideo(models.Model):
    # ... 其他字段
    title = models.CharField(max_length=255)

    class Meta:
        indexes = [
            models.Index(fields=['title'], name='ytvideo_title_idx'),
            # 全文搜索专用索引(可选)
            models.Index(fields=['search'], name='ytvideo_search_idx', opclasses=['gin'])
        ]

三、避免修改数据库对象,优化序列化逻辑

原代码中动态给video.thumbnail赋值会修改DB对象,容易引发意外。改用序列化器的context传递缩略图:

1. 修改YTVideoSerializer

class YTVideoSerializer(serializers.ModelSerializer):
    thumbnail = serializers.SerializerMethodField()

    class Meta:
        model = YTVideo
        fields = ['id', 'title', 'channel', 'thumbnail', ...]  # 你的其他字段

    def get_thumbnail(self, obj):
        return self.context.get('thumbnail')

2. 在get_videos中传递context

serializer = YTVideoSerializer(videos, many=True, context={'thumbnail': thumbnail})
all_videos_data.extend(serializer.data)

四、进阶优化:缓存与索引

1. 缓存查询结果

如果视频数据不是实时更新,用Django缓存缓存结果,避免重复查询:

from django.core.cache import cache

def get_videos(self, obj):
    cache_key = f'component_videos_{obj.pk}'
    cached_data = cache.get(cache_key)
    if cached_data:
        return cached_data

    # ... 上面的查询与序列化逻辑

    # 缓存1小时(可根据需求调整)
    cache.set(cache_key, all_videos_data, timeout=3600)
    return all_videos_data

2. 给排序字段加索引

确保order_by使用的字段(比如search.ordering_field对应的YTVideo字段)有数据库索引,避免排序时全表扫描:

class YTVideo(models.Model):
    # 假设排序字段是publish_date
    publish_date = models.DateTimeField(db_index=True)

优化后的完整get_videos示例:

from django.core.cache import cache
from django.db.models import Q
from django.contrib.postgres.search import SearchVector, SearchQuery
import operator

def get_videos(self, obj):
    cache_key = f'component_videos_{obj.pk}'
    cached_data = cache.get(cache_key)
    if cached_data:
        return cached_data

    all_videos_data = []
    # 预取关联数据,避免N+1
    searches = obj.search_tags.filter(is_active=True).select_related('channel', 'channel__category')
    
    for search in searches:
        thumbnail = search.thumbnail.url if search.thumbnail else None
        # 预处理标签
        tags = [tag.strip() for tag in search.content.split(',') if tag.strip()]
        if not tags:
            continue
        
        # 构建查询条件
        channel_q = Q(channel=search.channel)
        category_q = Q(channel__category=search.category)  # 用外键而非名称
        # 全文搜索替代icontains
        search_query = SearchQuery(' | '.join(tags), search_type='websearch')
        
        videos = YTVideo.objects.select_related('channel').annotate(
            search=SearchVector('title')
        ).filter(
            (channel_q | category_q) & Q(search=search_query)
        ).order_by(search.ordering_field)[:search.limit]
        
        # 序列化并传递缩略图
        serializer = YTVideoSerializer(videos, many=True, context={'thumbnail': thumbnail})
        all_videos_data.extend(serializer.data)
    
    cache.set(cache_key, all_videos_data, timeout=3600)
    return all_videos_data

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 06:21:02